Пример кода на C ++:
class A {
public:
A(int) {}
};
class B : public virtual A {
public:
B(int b) : A(b) {}
};
class C : virtual public A {
public:
C(int c) : A(c) {}
};
class D : public B, public C {
public:
D() : B(1), C(2){};
};
Это типичный код (решение) для алмазной проблемы. Я знаю, почему используется виртуальное ключевое слово. Но внутренний механизм, с помощью которого компилятор решает эту проблему, мне неизвестен. Теперь я столкнулся с двумя различными теориями об указанном механизме, которые изложены ниже.
Когда класс наследуется с виртуальным ключевым словом, компилятор добавляет указатель виртуальной базы в производный класс. Я проверил размер производного класса и да, он включает в себя размер дополнительного указателя. Но я не знаю, на что он указывает и как он работает, когда член класса A упоминается в классе D в приведенном выше примере.
Для каждого из конструкторов компилятор создает две версии каждого определения, предоставленного программистом. Узнал от эта ссылка
например в приведенном выше коде.
Компилятор сгенерирует 2 версии конструктора C
C(int){} // Version1
C(int):A(int){} // Version2
И две разные версии конструктора B
B(int){} // Version1
B(int):A(int){} // Version2
Поэтому, когда D построен, компилятор сгенерирует любой из следующих кодов
D() : B(), C(2) {} // Version1
D() : B(1), C() {} // Version2
Чтобы удостовериться, что создан только один экземпляр A и, следовательно, избегается дублирование A.
Пожалуйста, помогите мне понять внутренний механизм.
Обычное использование (не указано ни в одном стандарте!) — сначала создать экземпляр виртуально унаследованного объекта и поместить указатель на него в vtables. Итак, вот что происходит:
Но это не более чем теоретический ответ о том, что может быть реализацией. Я не говорю, что фактическая реализация (скажем, gcc, clang или microsoft vc) следует именно этому. Но вы можете использовать его, например, если вам нужно имитировать виртуальное наследование на простом языке Си.
Других решений пока нет …