Hay una diferencia considerable cuando se tiene plantillas y empezar a tomar clase base (s) como parámetro de plantilla (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);
}
La parte divertida de esto es que ahora se puede definir la interfaz y no la interfaz funciones más tarde para definir clases. Esto es útil para interfaces de interfuncionamiento entre bibliotecas (no confíe en esto como un proceso de diseño estándar de una sola biblioteca ). No le cuesta nada permitir esto para todas sus clases; puede incluso typedef
B si lo desea.
Tenga en cuenta que, si hace esto, también puede declarar los constructores de copiar/mover como plantillas: permitir construir desde diferentes interfaces le permite 'fundir' entre diferentes tipos de B<>
.
Es cuestionable si debe agregar soporte para const A&
en t_hello()
. La razón habitual para esta reescritura es alejarse de la especialización basada en herencia a la basada en plantillas, principalmente por motivos de rendimiento. Si continúa admitiendo la interfaz anterior, difícilmente podrá detectar (o disuadir) el uso anterior.
En C++ 11 puede escribir "void hello() override {}" para declarar explícitamente que está anulando un método virtual. El compilador fallará si no existe un método virtual base, y tiene la misma legibilidad que colocar "virtual" en la clase descendiente. – ShadowChaser
En realidad, en C++ 11 de gcc, escribir invalidar hello() anular {} en la clase derivada es correcto porque la clase base ha especificado que el método hello() es virtual. En otras palabras, el uso de la palabra virtual en la clase _derived_ no es necesario/obligatorio, para gcc/g ++ de todos modos. (Estoy usando la versión 4.9.2 de gcc en un RPi 3). Pero es una buena práctica incluir de todos modos la palabra clave virtual en el método de la clase derivada. – Will