Подтвердить что ты не робот

Почему это наследование класса алмазов не то, что я ожидаю?

Рассмотрим:

#include <iostream>

using namespace std;

class A {// base class
private:
    int data;
public:
    A(int data = 0) 
    {
        this->data = data;
    }
    void show() 
    {
        cout << data << endl;
        return;
    }
};

class B : virtual public A {
public:
    B(int data = 0) :
        A(data) {
    }
};

class C : virtual public A {
public:
    C(int data = 0) :
        A(data) {
    }
};

class D : public B, public C {
public:
    D(int dataB = 0, int dataC = 0) :
        B(dataB),
        C(dataC) {
    }
};

int main() {
    D d(1, 2);
    d.B::show();
    d.C::show();
    return 0;
}

Вышеприведенный код - диаграмма наследования класса алмаза. Базовый класс - A. Я использую виртуальное наследование, чтобы избежать проблемы с алмазом. Но почему вывод этой программы 0,0, а не 1,2, как я ожидаю?

B конструктор передается data=1, а в его списке инициализаторов он вызывает A с data. C подобный конструктор передается data=2, а его список инициализаторов вызывает A с data.

Затем мы задаем подбогам B и C для show их значение. И мы получаем 0 0 не 1 2, как я ожидаю.

4b9b3361

Ответ 1

Если у вас есть эта схема с виртуальным наследованием, то до самого производного класса в иерархии (в данном случае D) можно вызвать конструктор общей базы (A) 1,2.

Поскольку ваш конструктор для A имеет параметр по умолчанию data = 0, его можно использовать как конструктор по умолчанию. И это то, что происходит, общий под-объект A получает по умолчанию, поскольку вы опустили его из списка инициализации члена D.

Если вы удалите значение по умолчанию для data, вы получите хорошую ошибку компилятора для акцента:

A(int data) 
{
    this->data = data;
}

В g++ это результат с:

main.cpp: In constructor 'D::D(int, int)':
main.cpp:37:16: error: no matching function for call to 'A::A()'
         C(dataC) {

1 Помните, что с виртуальным наследованием существует только один под-объект типа A. И к нему относятся и подтексты B и C. Невозможно, чтобы ваши вызовы show печатали разные вещи, так как они получают доступ к тем же данным. Вот почему это относится к самому производному классу, поэтому нет никакой двусмысленности.

[class.mi/4]

Спецификатор базового класса, который не содержит ключевое слово virtual, указывает не виртуальный базовый класс. Спецификатор базового класса, который содержит ключевое слово virtual, указывает виртуальный базовый класс. Для каждого отдельного появления не виртуального базового класса в решетке класса самого производного класса наиболее производный объект должен содержать соответствующий отдельный подобъект базового класса этого типа. Для каждого отдельного базового класса, который указан виртуальным, наиболее производный объект должен содержать один подобъект базового класса этого типа.

[class.base.init/13.1]

В конструкторе без делегирования инициализация выполняется в следующем порядке:

  • Во-первых, и только для конструктора самого производного класса виртуальные базовые классы инициализируются в порядке их появления на обратном пути слева направо слева направо ациклический граф базовых классов, где "слева направо" - это порядок появления базовых классов в базовом-спецификаторе-списке производного класса.

  • ...

2 Итак, если вы хотите построить A с конкретными данными, вы должны определить D::D() следующим образом:

D(int dataA = 0) :
  A(dataA) {
}

Ответ 2

Когда у вас есть наследование virtual, базовый класс virtual инициализируется конструктором самого производного класса.

D(int dataB = 0, int dataC = 0) :
    B(dataB),
    C(dataC) {}

эквивалентно:

D(int dataB = 0, int dataC = 0) :
    A(),
    B(dataB),
    C(dataC) {}

который в вашем случае совпадает с

D(int dataB = 0, int dataC = 0) :
    A(0),
    B(dataB),
    C(dataC) {}

Если вы не создадите экземпляр B,

B(int data = 0) :
    A(data) {
}

совпадает с

B(int data = 0) {}

без кода для инициализации A, поскольку A уже инициализируется в конструкторе D.

То же самое относится к реализации C::C(int data).

Это объясняет вывод, который вы видите.

Ответ 3

Я думаю, вы неправильно поняли концепцию наследования virtual и "проблему с алмазом". С наследованием virtual вы создаете шаблон наследования бриллиантов, но из вашего сообщения кажется, что вы хотите избежать этого и вместо этого иметь две базы A, одну из B и другую из C. Чтобы получить это, просто избегайте виртуального наследования в B и C, когда ваш код напишет 1 2.

Случайно, если только B имеет наследование virtual от A, но C обычного наследования от A, то D будет иметь две базы A, но это из B снова по умолчанию инициализировано (как объяснено в других ответах).