Страницы

Поиск по вопросам

вторник, 19 марта 2019 г.

Конструирование объкта производного класса при множественном (“ромбовидном”) наследовании в C++

Возник вопрос по теме множественного наследования в C++. Допустим, у нас есть некоторый код, представляющий собой случай «ромбовидного» наследования, в котором класс A является виртуальным базовым классом для классов B и С, которые, в свою очередь, являются базовыми для класса D
#include
class A { public: int a; public: A() : a(0) {} A(int _a) : a(_a) {}
};
class B : virtual public A { public: int b; public: B(int _a, int _b) : A(_a), b(_b) {} };
class C : virtual public A { public: int c; public: C(int _a, int _c) : A(_a), c(_c) {} };
class D : public B, public C { public: int d; public: D(int _a, int _b, int _c, int _d) : B(_a, _b), C(_a, _c), d(_d) {} };
int main() {
D obj(1, 2, 3, 4);
std::cout << "obj.a = " << obj.a << std::endl; // Выводит: 0 std::cout << "obj.b = " << obj.b << std::endl; // Выводит: 2 std::cout << "obj.c = " << obj.c << std::endl; // Выводит: 3 std::cout << "obj.d = " << obj.d << std::endl; // Выводит: 4
return 0; }
Ясно, что наличие виртуального базового класса А гарантирует, что при конструировании объекта типа D мы будем иметь только одну копию подобъекта класса А. Это приводит к совсем другому поведению, отличному от того, если бы базовый класс был не виртуальным. По результатам выполненного кода видно, что за создание этого единственного экземпляра подобъета класса А, отвечает конструктор по-умолчанию в классе A, причем его вызов происходит в конструкторе класса D посредством списка инициализации, а не в непосредсвенных потомках B и С, которые напрямую наследуют от А. Ясно, что вместо генерирования компилятором вывоза конструктора по-умолчанию можно явно указать вызов любого другого конструктора класса A в списке инициализации конструктора класса D.
Вопрос в следующем: вызывая конструкторы B(_a, _b) и C(_a, _c) непосредственных классов-предков класса D, мы должны по цепочке прийти к явному вызову конструктора класса A в списках инициализации класса B и класса C, но этого не происходит при наличии виртуального базового класса. Получается код инициализации A(_a) в конструкторах B и C классов просто игнорируется компилятором и его можно безболезненно убрать, не меняя поведения?


Ответ

Убрать можно, на насколько это будет безболезненно в вашем абстрактном случае сказать не реально.
И к слову говоря, у вас класс A не является виртуальным, так как в нём нет виртуальных членов функций и указателя _vptr на vtable и т.д. и т.п.
По причине виртуального наследования классов B и C от A, класс D напрямую наследует A, а не через B и С для избежания одинаковых копий объекта А в D - поэтому если явно вызвать параметризированный конструктор А в D у вас потом выведется 1, 2, 3, 4.
Тоесть виртуальное наследование гарантирует прямое наследование без промежуточных предков, что в свою очередь обеспечивает только одну копию предка в наследнике.

Комментариев нет:

Отправить комментарий