Большинство современных компиляторов (GCC, Clang, MSVC) реализуют наследование через:
Базовые классы размещаются в начале памяти производного класса:
class Base {
int x;
};
class Derived : public Base {
int y;
};
// В памяти: [Base_part][Derived_part]
// Layout: [int x][int y]
Для классов с виртуальными функциями создается vtable:
class Base {
public:
virtual void foo() {}
virtual ```Base() {}
};
class Derived : public Base {
public:
void foo() override {}
};
// vtable для Base: [&Base::foo][&Base::```Base]
// vtable для Derived: [&Derived::foo][&Derived::```Derived]
Каждый производный класс содержит:
// Примерный layout в памяти:
[Base members][Derived members][vptr]
Более сложная структура с несколькими базовыми классами:
class Base1 { int x; };
class Base2 { int y; };
class Derived : public Base1, public Base2 { int z; };
// Memory layout:
[Base1][Base2][Derived]
// Или: [int x][int y][int z]
Для решения проблемы "ромбовидного" наследования:
class A { int x; };
class B : virtual public A { int y; };
class C : virtual public A { int z; };
class D : public B, public C { int w; };
// Memory layout содержит указатель на общую часть A
// Примерный вызов виртуальной функции
obj->vptr[0](); // Вызов первого виртуального метода
struct Base_vtable {
void (*foo)(Base*);
void (*destructor)(Base*);
};
struct Base {
Base_vtable* vptr;
};
struct Derived {
Base base; // Вложенная структура
Derived_vtable* vptr; // Своя vtable
};
Основные принципы реализации наследования:
Понимание этих механизмов помогает: