Có sự khác biệt đáng kể khi bạn có các mẫu và bắt đầu tham gia lớp cơ sở (es) như mẫu tham số (s):
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);
}
Các phần thú vị của nó là bây giờ bạn có thể định nghĩa giao diện và phi giao diện chức năng sau để xác định các lớp. Điều đó rất hữu ích cho các giao diện interworking giữa các thư viện (không dựa vào điều này như là một quy trình thiết kế chuẩn của một thư viện đơn). Bạn không mất chi phí để cho phép điều này cho tất cả các lớp học của mình - thậm chí bạn có thể typedef
B để làm điều gì đó nếu bạn muốn. Lưu ý rằng, nếu bạn làm điều này, bạn có thể muốn khai báo các nhà xây dựng sao chép/di chuyển như các khuôn mẫu: quá cho phép xây dựng từ các giao diện khác nhau cho phép bạn 'bỏ' giữa các loại khác nhau B<>
.
Điều đáng ngờ là bạn nên thêm hỗ trợ cho const A&
trong t_hello()
. Lý do thông thường cho việc viết lại này là di chuyển ra khỏi chuyên môn dựa trên kế thừa 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ũ.
Trong C++ 11 bạn có thể viết "void hello() override {}" để khai báo rõ ràng rằng bạn đang ghi đè phương thức ảo. Trình biên dịch sẽ thất bại nếu một phương thức ảo cơ sở không tồn tại, và nó có khả năng đọc giống như đặt "ảo" trên lớp con cháu. – ShadowChaser
Thực ra, trong C++ 11 của gcc, viết void hello() override {} trong lớp dẫn xuất là tốt vì lớp cơ sở đã xác định rằng phương thức hello() là ảo. Nói cách khác, việc sử dụng từ ảo trong lớp _derived_ là không cần thiết/bắt buộc, cho gcc/g ++. (Tôi đang sử dụng gcc phiên bản 4.9.2 trên một RPi 3) Nhưng thực tế tốt là bao gồm từ khóa ảo trong phương thức của lớp dẫn xuất. – Will