В проблеме с бриллиантом в 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 родительских класса, то есть два и три.
Код выдаст мне ошибку времени компиляции, если я не вызову конструктор один из четвертого класса. Пожалуйста, объясните мне, почему мы должны это сделать.
Ответы
Ответ 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 вызывается по умолчанию, даже если родительские классы явно вызывают параметризованный конструктор.