Как виртуальное наследование С++ реализовано в компиляторах?
Как компиляторы реализуют виртуальное наследование?
В следующем коде:
class A {
public:
A(int) {}
};
class B : public virtual A {
public:
B() : A(1) {}
};
class C : public B {
public:
C() : A(3), B() {}
};
Создает ли компилятор два экземпляра функции B::ctor
, один без вызова A(1)
и один с ним? Поэтому, когда B::constructor
вызывается из конструктора производного класса, используется первый экземпляр, в противном случае второй.
Ответы
Ответ 1
Это зависит от реализации. GCC (см. этот вопрос), например, испустит два конструктора: один с вызовом A(1)
, другой без него.
B1()
B2() // no A
Когда B строится, вызывается "полная" версия:
B1():
A(1)
B() body
Когда C построено, вместо него вызывается базовая версия:
C():
A(3)
B2()
B() body
C() body
Фактически, два конструктора будут выпущены, даже если нет виртуального наследования, и они будут идентичными.
Ответ 2
Компилятор не создает другого конструктора B, но игнорирует A(1)
. Поскольку A
фактически унаследован, он сначала создается с его конструктором по умолчанию. И поскольку он уже сконструирован при вызове B()
, часть A(1)
игнорируется.
Изменить. Я пропустил часть A(3)
в списке инициализации конструктора C
. Когда используется виртуальное наследование, только самый производный класс инициализирует виртуальные базовые классы. Таким образом, A
будет построен с A(3)
, а не его конструктором по умолчанию. Остальное остается - любые инициализации A
промежуточным классом (здесь B
) игнорируются.
Отредактируйте 2, пытаясь ответить на фактический вопрос относительно реализации вышеперечисленного:
В Visual Studio (по крайней мере, 2010) вместо двух реализаций B()
используется флаг. Поскольку B
фактически наследует от A
, прежде чем он называет конструктор A
, флаг проверяется. Если флаг не установлен, вызов A()
пропускается. Затем в каждом классе, полученном из B
, флаг reset после инициализации A
. Тот же механизм используется для предотвращения C
от инициализации A
, если он является частью некоторого D
(если D
наследует от C
, D
будет инициализировать A
).
Ответ 3
Itanium С++ ABI - полезный ресурс для всех вопросов, таких как "как это можно реализовать с помощью компиляторов С++".
В частности 5.1.4 Другие специальные функции и сущности перечисляют различные специальные функции-члены для разных целей:
<ctor-dtor-name> ::= C1 # complete object constructor
::= C2 # base object constructor
::= C3 # complete object allocating constructor
::= D0 # deleting destructor
::= D1 # complete object destructor
::= D2 # base object destructor
Раздел 1.1 Определения полезен (но не завершен):
деструктор базового объекта класса T
Функция, которая запускает деструкторы для нестатических членов данных из T и не виртуальных прямых базовых классов T.
полный деструктор объекта класса T
Функция, которая в дополнение к действиям, требуемым для деструктора базового объекта, запускает деструкторы для виртуальных базовых классов от T.
удаление деструктора класса T
Функция, которая в дополнение к действиям, требуемым для полного деструктора объекта, вызывает соответствующую функцию освобождения (т.е. оператор delete) для T.
Из этих определений цель полного конструктора объектов и конструктора базового объекта очевидна.
Ответ 4
Я предлагаю вам прочитать некоторые документы. Эти два действительно интересны, особенно первые, поскольку он исходит от отца С++:
[1] Бьярне Страуструп. Множественное наследование для С++. Пользователи C/С++
Journal, май 1999.
[2] J. Templ. Системный подход к многократной реализации наследования.
ACM SIGPLAN, Volume 28, No. 4 April 1993.
Я использовал их в качестве основных ссылок, проводя семинар (как студент) по множественному наследованию в своем университете.
Ответ 5
Как упоминалось ранее, это зависит от реализации компилятора.
Но, как правило, каждый раз, когда программист добавляет новый метод, он сохраняется в коде, даже если есть другой метод с тем же идентификатором. в другом месте ( "overriden" или "overloaded" ).
Код для каждого метода хранится только один раз, поэтому, если класс наследует и использует тот же метод из родительского класса, внутри он использует указатель на код, он не дублирует код.
Если родительский класс определяет виртуальный метод, и если дочерний класс переопределяет его, оба метода сохраняются. Каждый класс имеет нечто, называемое "Таблица виртуальных методов", где есть таблица указателей на каждый метод.
Не беспокойтесь о производительности, компилятор не дублирует код для методов.