В задаче с бриллиантами в С++, зачем нам вызывать конструктор grand_parent из дочернего класса?

Пожалуйста, прочитайте код, чтобы понять ситуацию.

#include <iostream>
using namespace std;
class one
{
protected:
    int x;
public:
    one(int a)
    {
        x=a;
        cout << "one cons called\n";
    }
    void display(void)
    {
        cout << "x = " << x << endl;
    }
    ~one()
    {
        cout << "one destroy\n";
    }
};
class two : virtual protected one
{
protected:
    int y;
public:
    two(int a,int b) : one(a),y(b)
    {
        cout << "two cons called\n";
    }
    void display(void)
    {
        one::display();
        cout << "y = " << y << endl;
    }
    ~two()
    {
        cout << "two destroy\n";
    }
};

class three : protected virtual one
{
protected:
    int z;
public:
    three(int a,int b) : one(a),z(b)
    {
        cout << "Three cons called\n";
    }
    void display(void)
    {
        one::display();
        cout << "z = " << z << endl;
    }
    ~three()
    {
        cout << "three destroy\n";
    }
};

class four : private two, private three
{
public:
    four(int a,int b,int c) :one(a), two(a,b),three(a,c)
    {
        cout << " four cons called\n";
    }
    void display(void)
    {
        one::display();
        cout << "y = " << y << endl;
        cout << "z = " << z << endl;
    }
    ~four()
    {
        cout << "four destroy\n";
    }
};
int main()
{
    four ob(1,2,3);
    ob.display();
    return 0;
}

Если я заменю код

four(int a,int b,int c) :one(a), two(a,b),three(a,c)

с

four(int a,int b,int c) :two(a,b),three(a,c)

сообщение об ошибке, например: нет соответствующей функции для вызова 'one :: one ()' в моем кодовом блоке ide.

Как вы можете видеть, это код, основанный на проблеме алмаза. Первый класс — это класс grand_parent. Второй и третий классы служат родительским классом, а четвертый класс — дочерним. Поэтому я использовал ключевое слово virtual, чтобы избежать двусмысленности. Все, что я здесь понимаю, кроме одной вещи. Я знаю, что когда родительский класс имеет параметризованный конструктор, нам нужно предоставить аргументы этому конструктору из производного класса. Итак, зачем же нужно предоставлять аргумент конструктору one, где четвертый класс имеет только 2 родительских класса, то есть two и three . Код выдаст мне ошибку времени компиляции, если я не вызову конструктор один из класса четыре. Пожалуйста, объясните мне, почему мы должны это сделать.


person Community    schedule 26.07.2019    source источник
comment
Есть несколько подозрительных вещей в коде и классах, которые вы показываете. Например, почему вы переопределяете не-виртуальную функцию display во всех классах? А почему у вас private наследство? Частное наследование обычно рекомендуется против композиции.   -  person Some programmer dude    schedule 26.07.2019
comment
@Someprogrammerdude. Я написал этот код для обучения, а не для реального проекта.   -  person    schedule 26.07.2019
comment
Возможный дубликат: stackoverflow.com/questions/44324583/   -  person Caleth    schedule 26.07.2019


Ответы (3)


Наследование virtual в вашей иерархии устраняет неоднозначность существования базового класса one, гарантируя, что только один единственный экземпляр one хранится в подклассах two или three. Напомним, что при наследовании какого-либо класса производный экземпляр всегда будет хранить базовый экземпляр где-то внутри, поэтому наследование virtual гарантирует, что экземпляры one внутри two и three в какой-то мере «переопределяются» любым классом, находящимся ниже по иерархии наследования.

Теперь вопрос: кто отвечает за инициализацию этого единственного экземпляра one? Должно быть two или three? Явно не оба, так как экземпляр только один. И вот вы: это всегда наиболее производный класс, который отвечает за инициализацию one, и это имеет смысл: экземпляр, в который встроена копия базового класса, должен его инициализировать.

Вот как выглядит иерархия классов со встроенными экземплярами базового класса без four и с наследованием four плюс virtual:

              +----------+                           +----------+
              |   one    |                           |   one    |
              +----+-----+                           +----+-----+
                   |                                      |
                   |                                      |
         +-------+-----------+           virtual +--------+--------+ virtual
         |                   |                   |                 |
         |                   |                   |                 |
+--------+-------+   +-------+-------+      +----+----+       +----+----+
|      two       |   |      three    |      |  two    |       |  three  |
| +------------+ |   | +----------+  |      +----+----+       +----+----+
| |   one      | |   | |   one    |  |           |                 |
| +------------+ |   | +----------+  |           +--------+--------+
|  => must init! |   | => must init! |                    |
+----------------+   +---------------+            +-------+--------+
                                                  |     four       |
                                                  | +------------+ |
                                                  | |    one     | |
                                                  | +------------+ |
                                                  | => must init!  |
                                                  +----------------+

Вы можете думать об этом механизме следующим образом: virtual наследование дает экземпляру базового класса virtual-ность, и это включает в себя создание экземпляра - эта ответственность передается вниз по иерархии.

person lubgr    schedule 26.07.2019
comment
Вы создали эту диаграмму вручную или с помощью инструмента? - person 0x499602D2; 27.07.2019
comment
@0x499602D2 Первый набросок здесь, затем небольшое визуальное редактирование блоков в vim. - person lubgr; 27.07.2019

Скажем, у вас есть следующий бриллиант:

     Base
    /    \
 Left    Right
    \    /
     Down

Класс Base может быть очень простым, он имеет единственный член int, который инициализируется конструктором:

struct Base
{
    Base(int x) 
        : x(x)
    {}
    virtual ~Base() = default;
    int x;
};

Поскольку Left наследуется от Base, его конструктор может передавать аргументы конструктору Base. Здесь, если вы создадите объект Left, его член x будет 1:

struct Left : virtual Base
{
    Left() : Base(1)
    {}
};

Другой класс, Right, также наследуется от Base. Это означает, что его конструктор также может передавать аргументы конструктору Base. Здесь его x член будет 2:

struct Right : virtual Base
{
    Right() : Base(2)
    {}
};

Теперь самое интересное: что произойдет, если вы наследуете как Left, так и Right?

// This does not compile.
struct Down : Left, Right
{
    Down() : Left(), Right()
    {}
};

И Left, и Right вызывают конструктор Base, но используют разные аргументы. Должен ли теперь компилятор использовать часть Base(1) из Left или он должен использовать часть Base(2) из Right? Ответ прост: он не использует ни того, ни другого! Компилятор оставляет выбор за вами и позволяет указать, какой конструктор следует использовать:

// Hooray, this version compiles.
struct Down : Left, Right
{
    Down() : Base(42), Left(), Right()
    {}
};
person pschill    schedule 26.07.2019

Алмазная проблема возникает, когда два суперкласса одного класса имеют общий базовый класс. Решение этой проблемы — ключевое слово «Виртуальный». В общем случае нельзя вызывать конструктор прародителя напрямую, его нужно вызывать через родительский класс. Это разрешено только тогда, когда мы используем ключевое слово «Виртуальный». ТАК, когда мы используем ключевое слово «виртуальный», конструктор по умолчанию класса прародителя вызывается по умолчанию, даже если родительские классы явно вызывают параметризованный конструктор.

person Mukesh Prajapat    schedule 26.07.2019