Istnieje znaczna różnica, gdy masz szablony i zaczynasz brać klasy podstawowe jako parametry szablonu:
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);
}
Część zabawy jest to, że można teraz definiować funkcje interfejsu i non-Interface później do definiowania klas. Jest to przydatne do współpracy interfejsów między bibliotekami (nie polegaj na tym jako standardowym procesie projektowania pojedynczej biblioteki). Nic nie kosztuje, aby pozwolić na to dla wszystkich twoich zajęć - możesz nawet typedef
B do czegoś, jeśli chcesz.
Zauważ, że jeśli to zrobisz, możesz chcieć zadeklarować również konstruktory kopiowania / przenoszenia jako szablony: zezwolenie na konstruowanie z różnych interfejsów pozwala „rzutować” między różnymi B<>
typami.
Wątpliwe jest, czy należy dodać obsługę const A&
w t_hello()
. Zwykle powodem tego przepisywania jest odejście od specjalizacji opartej na dziedziczeniu na specjalizacji opartej na szablonie, głównie ze względu na wydajność. Jeśli nadal będziesz obsługiwać stary interfejs, nie będziesz w stanie wykryć (lub powstrzymać) starego użycia.