«Каковы последствия с точки зрения затрат памяти / производительности?»
Обычно никто, кроме тех, кто вообще использует виртуальные вызовы, хотя стандартом ничего не гарантируется с точки зрения производительности.
При нехватке памяти оптимизация «пустого базового класса» явно разрешает компилятору структурировать структуры так, чтобы добавление базового класса, не имеющего элементов данных, не увеличивало размер ваших объектов. Я думаю, что вам вряд ли придется иметь дело с компилятором, который этого не делает, но я могу ошибаться.
Добавление первой виртуальной функции-члена в класс обычно увеличивает объекты на размер указателя по сравнению с тем, если у них не было виртуальных функций-членов. Добавление дополнительных виртуальных функций-членов не имеет никакого дополнительного значения. Добавление виртуальных базовых классов может иметь дальнейшее значение, но вам это не нужно для того, о чем вы говорите.
Добавление нескольких базовых классов с помощью виртуальных функций-членов, вероятно, означает, что фактически вы получаете пустую оптимизацию базового класса только один раз, потому что в типичной реализации объекту потребуется несколько указателей vtable. Так что если вам нужно несколько интерфейсов для каждого класса, вы можете добавлять к размеру объектов.
Что касается производительности, то вызов виртуальной функции имеет чуть больше накладных расходов, чем вызов не виртуальной функции, и, что более важно, вы можете предположить, что он обычно (всегда?) Не будет встроенным. Добавление пустого базового класса обычно не добавляет никакого кода в конструкцию или уничтожение, потому что пустой базовый конструктор и деструктор могут быть встроены в производный код конструктора / деструктора класса.
Существуют приемы, которые можно использовать, чтобы избежать виртуальных функций, если вам нужны явные интерфейсы, но вам не нужен динамический полиморфизм. Однако, если вы пытаетесь эмулировать Java, тогда я предполагаю, что это не так.
Пример кода:
#include <iostream>
// A is an interface
struct A {
virtual ~A() {};
virtual int a(int) = 0;
};
// B is an interface
struct B {
virtual ~B() {};
virtual int b(int) = 0;
};
// C has no interfaces, but does have a virtual member function
struct C {
~C() {}
int c;
virtual int getc(int) { return c; }
};
// D has one interface
struct D : public A {
~D() {}
int d;
int a(int) { return d; }
};
// E has two interfaces
struct E : public A, public B{
~E() {}
int e;
int a(int) { return e; }
int b(int) { return e; }
};
int main() {
E e; D d; C c;
std::cout << "A : " << sizeof(A) << "\n";
std::cout << "B : " << sizeof(B) << "\n";
std::cout << "C : " << sizeof(C) << "\n";
std::cout << "D : " << sizeof(D) << "\n";
std::cout << "E : " << sizeof(E) << "\n";
}
Вывод (GCC на 32-битной платформе):
A : 4
B : 4
C : 8
D : 8
E : 12