Có một sự khác biệt đáng kể khi bạn có các mẫu và bắt đầu lấy (các) lớp cơ sở làm (các) tham số mẫu:
struct None {};
template<typename... Interfaces>
struct B : public Interfaces
{
void hello() { ... }
};
struct A {
virtual void hello() = 0;
};
template<typename... Interfaces>
void t_hello(const B<Interfaces...>& b) // different code generated for each set of interfaces (a vtable-based clever compiler might reduce this to 2); both t_hello and b.hello() might be inlined properly
{
b.hello(); // indirect, non-virtual call
}
void hello(const A& a)
{
a.hello(); // Indirect virtual call, inlining is impossible in general
}
int main()
{
B<None> b; // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually
B<None>* pb = &b;
B<None>& rb = b;
b.hello(); // direct call
pb->hello(); // pb-relative non-virtual call (1 redirection)
rb->hello(); // non-virtual call (1 redirection unless optimized out)
t_hello(b); // works as expected, one redirection
// hello(b); // compile-time error
B<A> ba; // Ok, vtable generated, sizeof(b) >= sizeof(void*)
B<None>* pba = &ba;
B<None>& rba = ba;
ba.hello(); // still can be a direct call, exact type of ba is deducible
pba->hello(); // pba-relative virtual call (usually 3 redirections)
rba->hello(); // rba-relative virtual call (usually 3 redirections unless optimized out to 2)
//t_hello(b); // compile-time error (unless you add support for const A& in t_hello as well)
hello(ba);
}
Điều thú vị của nó là bây giờ bạn có thể định nghĩa các chức năng giao diện và phi giao diện sau này để xác định các lớp. Đó là hữu ích cho ảnh hưởng lẫn nhau giữa các giao diện thư viện (không dựa vào điều này như một quá trình thiết kế tiêu chuẩn của một đơn thư viện). Bạn không mất gì khi cho phép điều này cho tất cả các lớp học của bạn - thậm chí bạn có thể typedef
B đến một cái gì đó nếu bạn muốn.
Lưu ý rằng, nếu bạn làm điều này, bạn cũng có thể muốn khai báo các công cụ sao chép / di chuyển dưới dạng mẫu: cho phép xây dựng từ các giao diện khác nhau cho phép bạn 'truyền' giữa các B<>
loại khác nhau.
Đó là vấn đề cho dù bạn cần hỗ trợ thêm cho const A&
trong t_hello()
. Lý do thông thường cho việc viết lại này là để chuyển từ chuyên môn dựa trên thừa kế sang dựa trên mẫu, chủ yếu là vì lý do hiệu suất. Nếu bạn tiếp tục hỗ trợ giao diện cũ, bạn khó có thể phát hiện (hoặc ngăn chặn) việc sử dụng cũ.