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.