Основная проблема, которую необходимо решить, состоит в том, что если вы приведете указатель к самому производному типу к указателю на одну из его баз, указатель должен ссылаться на адрес в памяти, из которого каждый член типа может быть найден кодом который не знает о производных типах. В случае не виртуального наследования это обычно достигается с помощью точного макета, а это, в свою очередь, достигается путем добавления подобъекта базового класса и добавления дополнительных битов производного типа:
struct base { int x; };
struct derived : base { int y };
Макет для производных:
--------- <- base & derived start here
x
---------
y
---------
Если вы добавите второй производный и большинство производных типов (опять же, без виртуального наследования), вы получите что-то вроде:
struct derived2 : base { int z; };
struct most_derived : derived, derived 2 {};
С этим макетом:
--------- <- derived::base, derived and most_derived start here
x
---------
y
--------- <- derived2::base & derived2 start here
x
---------
z
---------
Если у вас есть объект most_derived
и вы связываете указатель / ссылку типа derived2
, он будет указывать на строку, помеченную derived2::base
. Теперь, если наследование от базы было виртуальным, то должен быть один экземпляр base
. Ради обсуждения просто предположим, что мы наивно удаляем второй base
:
--------- <- derived::base, derived and most_derived start here
x
---------
y
--------- <- derived2 start here??
z
---------
Теперь проблема в том, что если мы получим указатель на derived
, он будет иметь ту же структуру, что и оригинал, но если мы попытаемся получить указатель на derived2
, то макет будет отличаться, а код в derived2
не будет быть в состоянии найти x
участника. Нам нужно сделать что-то умнее, и именно здесь указатель вступает в игру. Добавляя указатель на каждый объект, который виртуально наследуется, мы получаем такой макет:
--------- <- derived starts here
base::ptr --\
y | pointer to where the base object resides
--------- <-/
x
---------
Аналогично для derived2
. Теперь за счет дополнительной косвенности мы можем найти подобъект x
через указатель. Когда мы можем создать most_derived
макет с одной базой, это может выглядеть так:
--------- <- derived starts here
base::ptr -----\
y |
--------- | <- derived2
base::ptr --\ |
z | |
--------- <--+-/ <- base
x
---------
Теперь код в derived
и derived2
теперь показывает, как получить доступ к базовому подобъекту (просто разыменование объекта-члена base::ptr
), и в то же время у вас есть один экземпляр base
. Если код в любом промежуточном классе обращается к x
, они могут сделать это, выполнив this->[hidden base pointer]->x
, и это будет разрешено во время выполнения в правильную позицию.
Важным моментом здесь является то, что код, скомпилированный на уровне derived
/ derived2
, может использоваться с объектом этого типа или любым производным объектом. Если бы мы написали второй объект most_derived2
, в котором порядок наследования был изменен, тогда их расположение y
и z
можно было бы поменять местами, а смещения от указателя на подобъекты derived
или derived2
до Подобъект base
будет другим, но код для доступа к x
будет таким же: разыменуйте свой собственный скрытый базовый указатель, гарантируя, что если метод в derived
является окончательным переопределением, а доступ base::x
тогда он найдет его независимо от окончательного макета.