Qual é o problema do diamante em C ++? Como identificá-lo e como corrigi-lo

A herança múltipla em C ++ é poderosa, mas uma ferramenta complicada, que geralmente leva a problemas se não for usada com cuidado – problemas como o Problema do Diamante.

Neste artigo, discutiremos o problema do diamante, como ele surge da herança múltipla e o que você pode fazer para resolver o problema.

Herança múltipla em C ++

A herança múltipla é um recurso da Programação Orientada a Objetos (OOP) em que uma subclasse pode herdar de mais de uma superclasse. Em outras palavras, uma classe filha pode ter mais de um pai.

A figura abaixo mostra uma representação pictórica de várias heranças.

No diagrama acima, a classe C tem a classe A e a classe B como pais.

Se considerarmos um cenário da vida real, uma criança herda de seu pai e de sua mãe. Portanto, uma criança pode ser representada como uma classe derivada com “Pai” e “Mãe” como seus pais. Da mesma forma, podemos ter muitos exemplos da vida real de herança múltipla.

Na herança múltipla, os construtores de uma classe herdada são executados na ordem em que são herdados. Por outro lado, os destruidores são executados na ordem inversa de sua herança.

Agora vamos ilustrar a herança múltipla e verificar a ordem de construção e destruição dos objetos.

Ilustração de código de herança múltipla

Para a ilustração de herança múltipla, programamos exatamente a representação acima em C ++. O código do programa é fornecido abaixo.

 #include<iostream>
using namespace std;
class A //base class A with constructor and destructor
{
public:
A() { cout << "class A::Constructor" << endl; }
~A() { cout << "class A::Destructor" << endl; }
};
class B //base class B with constructor and destructor
{
public:
B() { cout << "class B::Constructor" << endl; }
~B() { cout << "class B::Destructor" << endl; }
};
class C: public B, public A //derived class C inherits class A and then class B (note the order)
{
public:
C() { cout << "class C::Constructor" << endl; }
~C() { cout << "class C::Destructor" << endl; }
};
int main(){
C c;
return 0;
}

A saída que obtemos do programa acima é a seguinte:

 class B::Constructor
class A::Constructor
class C::Constructor
class C::Destructor
class A::Destructor
class B::Destructor

Agora, se verificarmos a saída, veremos que os construtores são chamados na ordem B, A e C, enquanto os destruidores estão na ordem inversa. Agora que sabemos os fundamentos da herança múltipla, passamos a discutir o Problema do Diamante.

O Problema do Diamante, Explicado

O Problema do Diamante ocorre quando uma classe filha herda de duas classes pai que compartilham uma classe de avós em comum. Isso é ilustrado no diagrama abaixo:

Aqui, temos uma classe Criança herdando das classes Pai e Mãe . Essas duas classes, por sua vez, herdam a classe Pessoa porque tanto o Pai quanto a Mãe são Pessoa.

Conforme mostrado na figura, a classe Child herda os traços da classe Person duas vezes – uma vez do pai e novamente da mãe. Isso dá origem a ambigüidade, uma vez que o compilador não consegue entender que caminho seguir.

Este cenário dá origem a um gráfico de herança em forma de diamante e é conhecido como "O Problema do Diamante".

Ilustração de código do problema do diamante

A seguir, representamos o exemplo acima de herança em formato de diamante programaticamente. O código é fornecido abaixo:

 #include<iostream>
using namespace std;
class Person { //class Person
public:
Person(int x) { cout << "Person::Person(int) called" << endl; }
};

class Father : public Person { //class Father inherits Person
public:
Father(int x):Person(x) {
cout << "Father::Father(int) called" << endl;
}
};

class Mother : public Person { //class Mother inherits Person
public:
Mother(int x):Person(x) {
cout << "Mother::Mother(int) called" << endl;
}
};

class Child : public Father, public Mother { //Child inherits Father and Mother
public:
Child(int x):Mother(x), Father(x) {
cout << "Child::Child(int) called" << endl;
}
};

int main() {
Child child(30);
}

A seguir está o resultado deste programa:

 Person::Person(int) called
Father::Father(int) called
Person::Person(int) called
Mother::Mother(int) called
Child::Child(int) called

Agora você pode ver a ambigüidade aqui. O construtor da classe Person é chamado duas vezes: uma vez quando o objeto da classe Pai é criado e a próxima quando o objeto da classe Mãe é criado. As propriedades da classe Person são herdadas duas vezes, gerando ambigüidade.

Como o construtor da classe Person é chamado duas vezes, o destruidor também será chamado duas vezes quando o objeto da classe Child for destruído.

Agora, se você entendeu o problema corretamente, vamos discutir a solução para o Problema do Diamante.

Como corrigir o problema do diamante em C ++

A solução para o problema do diamante é usar a palavra-chave virtual . Transformamos as duas classes pai (que herdam da mesma classe dos avós) em classes virtuais para evitar duas cópias da classe dos avós na classe filha.

Vamos mudar a ilustração acima e verificar o resultado:

Ilustração de código para corrigir o problema do diamante

 #include<iostream>
using namespace std;
class Person { //class Person
public:
Person() { cout << "Person::Person() called" << endl; } //Base constructor
Person(int x) { cout << "Person::Person(int) called" << endl; }
};

class Father : virtual public Person { //class Father inherits Person
public:
Father(int x):Person(x) {
cout << "Father::Father(int) called" << endl;
}
};

class Mother : virtual public Person { //class Mother inherits Person
public:
Mother(int x):Person(x) {
cout << "Mother::Mother(int) called" << endl;
}
};

class Child : public Father, public Mother { //class Child inherits Father and Mother
public:
Child(int x):Mother(x), Father(x) {
cout << "Child::Child(int) called" << endl;
}
};

int main() {
Child child(30);
}

Aqui, usamos a palavra-chave virtual quando as classes Father e Mother herdam a classe Person. Isso geralmente é chamado de “herança virtual”, que garante que apenas uma única instância da classe herdada (neste caso, a classe Person) seja passada adiante.

Em outras palavras, a classe Child terá uma única instância da classe Person, compartilhada pelas classes Pai e Mãe. Por ter uma única instância da classe Person, a ambigüidade é resolvida.

A saída do código acima é fornecida a seguir:

 Person::Person() called
Father::Father(int) called
Mother::Mother(int) called
Child::Child(int) called

Aqui você pode ver que o construtor da classe Person é chamado apenas uma vez.

Uma coisa a se notar sobre a herança virtual é que mesmo se o construtor parametrizado da classe Person for explicitamente chamado pelos construtores das classes Father e Mother por meio de listas de inicialização, apenas o construtor base da classe Person será chamado .

Isso ocorre porque há apenas uma única instância de uma classe base virtual que é compartilhada por várias classes que herdam dela.

Para evitar que o construtor base seja executado várias vezes, o construtor de uma classe base virtual não é chamado pela classe que herda dela. Em vez disso, o construtor é chamado pelo construtor da classe concreta.

No exemplo acima, a classe Child chama diretamente o construtor base da classe Person.

Relacionado: Um Guia para Iniciantes da Biblioteca de Modelos Padrão em C ++

E se você precisar executar o construtor parametrizado da classe base? Você pode fazer isso chamando-o explicitamente na classe Criança em vez de nas classes Pai ou Mãe.

O problema do diamante em C ++, resolvido

O Problema do Diamante é uma ambigüidade que surge na herança múltipla quando duas classes pai herdam da mesma classe avô e ambas as classes pai são herdadas por uma única classe filha. Sem usar herança virtual, a classe filha herdaria as propriedades da classe avô duas vezes, levando à ambigüidade.

Isso pode surgir com frequência no código do mundo real, por isso é importante abordar essa ambigüidade sempre que for detectada.

O problema Diamond é corrigido usando herança virtual, na qual a palavra-chave virtual é usada quando classes pai herdam de uma classe avós compartilhada. Ao fazer isso, apenas uma cópia da classe dos avós é feita, e a construção do objeto da classe dos avós é feita pela classe filha.