Иногда вам просто нужно увидеть некоторые коды / схемы :) Обратите внимание, что в Стандарте нет упоминаний об этой детали реализации.
Прежде всего, давайте посмотрим, как реализовать методы в C ++:
struct Base
{
void foo();
};
Это похоже на:
struct Base {};
void Base_foo(Base& b);
И на самом деле, когда вы смотрите на вызов метода в отладчике, вы часто видите аргумент this
в качестве первого параметра. Иногда его называют неявным параметром.
Теперь перейдем к виртуальной таблице. В C и C ++ можно использовать указатели для работы. Vtable - это, по сути, таблица указателей на функции:
struct Base
{
int a;
};
void Base_set(Base& b, int i) { b.a = i; }
int Base_get(Base const& b) { return b.a; }
struct BaseVTable
{
typedef void (*setter_t)(Base&, int);
typedef int (*getter_t)(Base const&);
setter_t mSetter;
getter_t mGetter;
BaseVTable(setter_t s, getter_t g): mSetter(s), mGetter(g) {}
} gBaseVTable(&Base_set, &Base_get);
Теперь я могу сделать что-то вроде:
void func()
{
Base b;
(*gBaseVTable.mSetter)(b, 3);
std::cout << (*gBaseVTable.mGetter)(b) << std::endl; // print 3
}
Теперь перейдем к наследству. Давайте создадим еще одну структуру
struct Derived: Base {}; // yeah, Base does not have a virtual destructor... shh
void Derived_set(Derived& d, int i) { d.a = i+1; }
struct DerivedBaseVTable
{
typedef void (*setter_t)(Derived&,int);
typedef BaseVTable::getter_t getter_t;
setter_t mSetter;
getter_t mGetter;
DerivedBaseVTable(setter_t s, getter_t g): mSetter(s), mGetter(g) {}
} gDerivedBaseVTable(&Derived_set, &Base_get);
И использование:
void func()
{
Derived d;
(*gDerivedBaseVTable.mSetter)(d, 3);
std::cout << (*gDerivedBaseVTable.mGetter)(d) << std::endl; // print 4
}
Но как это автоматизировать?
- вам нужен только один экземпляр vtable на класс, имеющий хотя бы одну виртуальную функцию
- каждый экземпляр класса будет содержать указатель на vtable в качестве первого атрибута (даже если вы не можете получить к нему доступ самостоятельно)
Теперь, что происходит в случае множественного наследования? Ну, наследование очень похоже на композицию в плане размещения памяти:
| Derived |
| BaseA | BaseB |
| vpointer | field1 | field2 | padding? | vpointer | field1 | field2 | padding? |
Таким образом, для MostDerived
будет две виртуальные таблицы: одна для изменения методов с BaseA
и одна для изменения методов с BaseB
.
Чистые виртуальные функции обычно представлены как нулевой указатель (просто) в соответствующем поле.
И, наконец, строительство и разрушение:
Строительство
BaseA
создается: сначала инициализируется vpointer, затем атрибуты, затем тело конструктора
BaseB
построено: vpointer, attribute, body
Derived
построено: заменить vpointers (оба), атрибуты, тело
Уничтожение
Derived is destructed
: тело деструктора, уничтожить атрибуты, вернуть базовые vpointers обратно
BaseB
разрушено: тело, атрибуты
BaseA
разрушено: тело, атрибуты
Я думаю, что это довольно всесторонне, я был бы рад, если бы некоторые гуру C ++ там могли проверить это и проверить, что я не сделал глупой ошибки. Кроме того, если чего-то не хватает, я был бы рад добавить это.