Como o C ++ lida com herança múltipla com um ancestral comum compartilhado?

13

Eu não sou um cara de C ++, mas sou forçado a pensar sobre isso. Por que a herança múltipla é possível em C ++, mas não em C #? (Eu sei do problema do diamante , mas não é isso que estou perguntando aqui). Como o C ++ resolve a ambiguidade de assinaturas de método idênticas herdadas de várias classes base? E por que o mesmo design não foi incorporado ao C #?

Sandeep
fonte
3
Por uma questão de completude, qual é o "problema do diamante", por favor?
precisa saber é o seguinte
1
@jcolebrand en.wikipedia.org/wiki/Multiple_inheritance veja o problema do diamante
l46kok
1
Claro, pesquisei no Google, mas como sei que é isso que Sandeep significa? Se você estiver indo para referência algo por um nome obscuro, quando "herança múltipla com um ancestral comum partilhado" é mais direto ...
jcolebrand
1
@ colcoland Editei-o para refletir o que recebi da pergunta. Suponho que ele se refira ao problema do diamante referenciado na Wikipédia a partir do contexto. Da próxima vez que soltar o comentário amigável, e usar a nova ferramenta de edição sugeriu brilhante :)
Earlz
1
@Earlz que só funciona quando eu entendo a referência. Caso contrário, eu estou fazendo uma edição ruim, e isso é ruim.
precisa saber é o seguinte

Respostas:

24

Por que a herança múltipla é possível em C ++, mas não em C #?

Penso (sem ter uma referência rígida), que em Java eles queriam limitar a expressividade da linguagem para facilitar a aprendizagem e porque o código que usa herança múltipla costuma ser muito complexo para seu próprio bem. E como a herança múltipla completa é muito mais complicada de implementar, também simplificou muito a máquina virtual (herança múltipla interage especialmente mal com o coletor de lixo, porque requer manter os ponteiros no meio do objeto (no início da base) )

E, ao projetar o C #, acho que eles observaram o Java, viram que a herança múltipla completa realmente não foi perdida e optaram por manter as coisas simples também.

Como o C ++ resolve a ambiguidade de assinaturas de método idênticas herdadas de várias classes base?

Isso não acontece . Há uma sintaxe para chamar explicitamente o método da classe base de base específica, mas não há como substituir apenas um dos métodos virtuais e, se você não substituir o método na subclasse, não será possível chamá-lo sem especificar a base classe.

E por que o mesmo design não foi incorporado ao C #?

Não há nada a incorporar.


Como Giorgio mencionou métodos de extensão de interface nos comentários, explicarei o que são mixins e como eles são implementados em vários idiomas.

As interfaces em Java e C # são limitadas apenas a métodos de declaração. Mas os métodos precisam ser implementados em cada classe que herda a interface. No entanto, existe uma grande classe de interfaces, na qual seria útil fornecer implementações padrão de alguns métodos em termos de outros. Exemplo comum é comparável (em pseudo-idioma):

mixin IComparable {
    public bool operator<(IComparable r) = 0;
    public bool operator>(IComparable r) { return r < this; }
    public bool operator<=(IComparable r) { return !(r < this); }
    public bool operator>=(IComparable r) { return !(r > this); }
    public bool operator==(IComparable r) { return !(r < this) && !(r > this); }
    public bool operator!=(IComparable r) { return r < this || r > this; }
};

A diferença da classe completa é que isso não pode conter nenhum membro de dados. Existem várias opções para implementar isso. Obviamente, herança múltipla é uma. Mas a herança múltipla é bastante complicada de implementar. Mas não é realmente necessário aqui. Em vez disso, muitas linguagens implementam isso dividindo o mixin em uma interface, implementada pela classe e um repositório de implementações de métodos, que são injetados na própria classe ou é gerada uma classe base intermediária e são colocadas lá. Isso é implementado em Ruby e D , será implementado em Java 8 e pode ser implementado manualmente em C ++ usando o padrão de modelo curiosamente recorrente . O acima, no formato CRTP, se parece com:

template <typename Derived>
class IComparable {
    const Derived &_d() const { return static_cast<const Derived &>(*this); }
public:
    bool operator>(const IComparable &r) const { r._d() < _d(); }
    bool operator<=(const IComparable &r) const { !(r._d() < _d(); }
    ...
};

e é usado como:

class Concrete : public IComparable<Concrete> { ... };

Isso não requer que nada seja declarado virtual como a classe base regular exigiria, portanto, se a interface for usada em modelos, deixa opções úteis de otimização em aberto. Observe que, em C ++, isso provavelmente ainda seria herdado como segundo pai, mas em idiomas que não permitem herança múltipla, ele é inserido na cadeia de herança única, por isso é mais parecido com

template <typename Derived, typename Base>
class IComparable : public Base { ... };
class Concrete : public IComparable<Concrete, Base> { ... };

A implementação do compilador pode ou não evitar o despacho virtual.

Uma implementação diferente foi selecionada em C #. Em C #, as implementações são métodos estáticos de classe completamente separada e a sintaxe da chamada de método é adequadamente interpretada pelo compilador se um método de nome determinado não existir, mas um "método de extensão" for definido. Isso tem a vantagem de que métodos de extensão podem ser adicionados à classe já compilada e a desvantagem de que tais métodos não podem ser substituídos, por exemplo, para fornecer uma versão otimizada.

Jan Hudec
fonte
Pode-se mencionar que a herança múltipla será introduzida no Java 8 com métodos de extensão de interface.
Giorgio
@ Giorgio: Não, a herança múltipla definitivamente não será introduzida em Java. Mixins será, o que é uma coisa muito diferente, embora abranja muitos motivos restantes para usar herança múltipla e a maioria dos motivos para usar o padrão de modelo curiosamente recorrente (CRTP) e funcione principalmente como CRTP, não como herança múltipla.
Jan Hudec
Eu acho que a herança múltipla não requer ponteiros no meio de um objeto. Caso isso acontecesse, a herança de várias interfaces também exigiria.
svick
@ Rick: Não, não. Mas a alternativa é muito menos eficiente, pois requer despacho virtual para acesso de membros.
precisa saber é o seguinte
+1 para uma resposta muito mais completa que a minha.
Nathan C. Tresch
2

A resposta é que ele não funciona corretamente no C ++ no caso de conflitos no namespace. Veja isso . Para evitar conflitos de espaço para nome, você precisa fazer todo tipo de rotação com ponteiros. Trabalhei na MS na equipe do Visual Studio e, pelo menos em parte, o motivo pelo qual eles desenvolveram delegação foi evitar completamente a colisão de namespace. Anteriormente, eu havia dito que eles também consideravam as interfaces parte da solução de herança múltipla, mas eu estava enganado. As interfaces são realmente incríveis e podem ser feitas para trabalhar em C ++, FWIW.

A delegação trata especificamente da colisão de namespace: você pode delegar para 5 classes e todos os 5 exportarão seus métodos para o seu escopo como membros da primeira classe. Do lado de fora, é uma herança múltipla.

Nathan C. Tresch
fonte
1
Acho muito improvável que esse tenha sido o principal motivo para não incluir o MI no C #. Além disso, esta postagem não responde à pergunta por que o MI funciona em C ++ quando você não tem "conflitos de namespace".
Doc Brown
@DocBrown Eu trabalhei na MS na equipe do Visual Studio e garanto que, pelo menos em parte, eles desenvolveram delegação e interfaces, para evitar a colisão de namespace. Quanto à qualidade da pergunta, meh. Use seu voto negativo, outros parecem pensar que é útil.
Nathan C. Tresch
1
Não tenho a intenção de recusar sua resposta, pois acho que é a correta. Mas sua resposta finge que o MI é completamente inutilizável em C ++, e é por isso que eles não o introduziram em C #. Embora eu não goste muito do MI em C ++, acho que não é completamente inaceitável.
Doc Brown
1
É uma das razões, e eu não quis dizer que isso nunca funciona. Quando algo não é determinístico na computação, costumo dizer que está "quebrado" ou que "não funciona" quando pretendo dizer "é como um vodu, pode ou não funcionar, acenda suas velas e ore". : D
Nathan C. Tresch
1
as "colisões de namespace" não são determinísticas?
Doc Brown