Hay una diferencia considerable cuando tiene plantillas y comienza a tomar clases básicas como parámetros de plantilla:
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 es que ahora puede definir funciones de interfaz y no de interfaz más adelante para definir clases. Eso es útil para interfuncionar interfaces 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; incluso podría typedef
B a algo si lo desea.
Tenga en cuenta que, si hace esto, es posible que también desee declarar copiar / mover constructores como plantillas: permitir construir desde diferentes interfaces le permite 'lanzar' entre diferentes B<>
tipos.
Es cuestionable si debe agregar soporte para const A&
in t_hello()
. La razón habitual para esta reescritura es alejarse de la especialización basada en herencia a una basada en plantilla, principalmente por razones de rendimiento. Si continúa admitiendo la interfaz anterior, difícilmente podrá detectar (o disuadir) el uso anterior.