Страницы

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

пятница, 24 января 2020 г.

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

#cpp


Возник вопрос по теме множественного наследования в 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 классов просто игнорируется компилятором и его можно безболезненно убрать, не меняя
поведения?
    


Ответы

Ответ 1



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

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

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