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

В проблеме с бриллиантом в c++, Зачем нам нужно вызывать конструктор 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)

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

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

4b9b3361

Ответ 1

Наследование 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 -ness, и это включает в себя создание экземпляра - эта ответственность передается по иерархии.

Ответ 2

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

     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()
    {}
};

Ответ 3

Проблема Алмаза возникает, когда два суперкласса класса имеют общий базовый класс. Решением этой проблемы является ключевое слово "Виртуальный". В общем случае нельзя напрямую вызывать конструктор дедушки и бабушки, он должен вызываться через родительский класс. Это разрешено только при использовании ключевого слова "Виртуальный". Итак, когда мы используем ключевое слово virtual, конструктор по умолчанию класса grandparent вызывается по умолчанию, даже если родительские классы явно вызывают параметризованный конструктор.