c++ inheritance constructor diamond-problem

En un problema de diamante en c++, ¿por qué necesitamos llamar al constructor grand_parent desde la clase secundaria?



inheritance diamond-problem (3)

Por favor lea el código para entender la situación.

#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; }

Si reemplazo el código

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

con

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

un mensaje de error como: no se produce una función coincidente para la llamada a ''one :: one ()'' en mi ide de bloque de código.

Como puede ver, este es un código basado en un problema de diamante. Donde la clase uno es la clase grand_parent. Clase dos y tres que sirven como clase principal y clase cuatro como clase secundaria. Así que usé la palabra clave virtual para evitar la ambigüedad. Todo lo que entiendo aquí a menos que sea 1 cosa. Sé que cuando una clase padre tiene un constructor parametrizado, necesitamos proporcionar argumentos a ese constructor desde la clase derivada. Entonces, ¿por qué es necesario proporcionar un argumento al constructor uno donde la clase cuatro tiene solo 2 clases primarias que son dos y tres? El código me dará un error de tiempo de compilación si no llamo al constructor uno de la clase cuatro. Por favor explícame por qué necesitamos hacerlo.


Digamos que tiene el siguiente diamante:

Base / / Left Right / / Down

La clase Base puede ser muy simple, tiene un solo miembro int que es inicializado por el constructor:

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

Como Left hereda de Base , su constructor puede pasar argumentos al constructor de Base . Aquí, si construyes un objeto Left , su miembro x será 1 :

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

La otra clase, Right , también hereda de Base . Esto significa que su constructor también puede pasar argumentos al constructor Base . Aquí, su miembro x será 2 :

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

Ahora viene la parte divertida: ¿qué sucede si heredas de Left y Right ?

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

Tanto Left como Right llaman al constructor Base , pero usan argumentos diferentes. ¿Debería el compilador usar ahora la parte Base(1) desde la Left o debería usar la parte Base(2) desde la Right ? La respuesta es simple: ¡No usa ninguno! El compilador le deja la opción y le permite especificar qué constructor debe usarse:

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


El problema del diamante ocurre cuando dos superclases de una clase tienen una clase base común. La solución para este problema es la palabra clave "virtual". En general, si no está permitido llamar al constructor del abuelo directamente, debe llamarse a través de la clase padre. Está permitido solo cuando usamos la palabra clave "Virtual". SO, cuando usamos la palabra clave ''virtual'', el constructor predeterminado de la clase abuelo se llama por defecto, incluso si las clases primarias llaman explícitamente al constructor parametrizado.


La herencia virtual en su jerarquía desambigua la existencia de la clase base one asegurándose de que solo una instancia única de one se almacene en subclases de two o three . Recuerde que al heredar alguna clase, una instancia derivada siempre almacenará una instancia base internamente, por lo que virtual herencia virtual asegura que las instancias de one dentro de two y three sean algo "anuladas" por cualquier clase más abajo en la jerarquía de herencia.

Ahora la pregunta es: ¿quién es responsable de inicializar esta única instancia? ¿Deberían ser two o three ? Claramente no ambos, ya que solo hay una instancia. Y aquí está: siempre es la clase más derivada la responsable de la inicialización de one , y eso tiene sentido: la instancia que incrusta una copia de clase base debe inicializarla.

Así es como se ve la jerarquía de clases con instancias de clase base incrustadas sin four y con four más herencia virtual :

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

Puede pensar en este mecanismo de esta manera: virtual herencia virtual le da a la instancia de la clase base virtual , y eso incluye la construcción de la instancia: esta responsabilidad se transmite por la jerarquía.