Существует значительная разница, когда у вас есть шаблоны и вы начинаете принимать базовый класс (ы) в качестве параметров шаблона:
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);
}
Самое интересное в том, что теперь вы можете определять интерфейсные и неинтерфейсные функции позже, чем определять классы. Это полезно для взаимодействия интерфейсов между библиотеками (не полагайтесь на это как на стандартный процесс проектирования отдельной библиотеки). Это ничего не стоит, чтобы позволить это для всех ваших классов - вы можете даже typedef
B к чему-то, если хотите.
Обратите внимание, что если вы сделаете это, вы можете также объявить конструкторы копирования / перемещения в качестве шаблонов: разрешение конструировать из разных интерфейсов позволяет вам «приводить» между разными B<>
типами.
Сомнительно, стоит ли вам добавлять поддержку const A&
в t_hello()
. Обычная причина такого переписывания - перейти от специализации на основе наследования к специализации на основе шаблонов, в основном из соображений производительности. Если вы продолжаете поддерживать старый интерфейс, вы вряд ли сможете обнаружить (или предотвратить) старое использование.