¿Cómo maneja C ++ la herencia múltiple con un ancestro común compartido?

13

No soy un chico de C ++, pero me veo obligado a pensar en esto. ¿Por qué es posible la herencia múltiple en C ++, pero no en C #? (Sé del problema del diamante , pero eso no es lo que pregunto aquí). ¿Cómo resuelve C ++ la ambigüedad de firmas de métodos idénticas heredadas de múltiples clases base? ¿Y por qué el mismo diseño no está incorporado en C #?

Sandeep
fuente
3
En aras de la integridad, ¿cuál es el "problema del diamante", por favor?
jcolebrand
1
@jcolebrand en.wikipedia.org/wiki/Multiple_inheritance ver problema de Diamante
l46kok
1
Claro, lo busqué en Google, pero ¿cómo sé que eso es lo que significa Sandeep? Si va a hacer referencia a algo con un nombre oscuro, cuando "Herencia múltiple con un ancestro común compartido" es más directo ...
jcolebrand
1
@jcolebrand Lo edité para reflejar lo que obtuve de la pregunta. Supongo que se refiere al problema del diamante al que se hace referencia en wikipedia desde el contexto. La próxima vez suelte el comentario amistoso, Y use la nueva herramienta de edición sugerida brillante :)
Earlz
1
@Earlz que solo funciona cuando entiendo la referencia. De lo contrario, estoy haciendo una mala edición, y eso es solo un mal juju.
jcolebrand

Respuestas:

24

¿Por qué es posible la herencia múltiple en C ++, pero no en C #?

Creo (sin tener una referencia dura), que en Java querían limitar la expresividad del lenguaje para hacer que el lenguaje sea más fácil de aprender y porque el código que usa la herencia múltiple es a menudo demasiado complejo por su propio bien. Y debido a que la herencia múltiple completa es mucho más complicada de implementar, también simplificó mucho la máquina virtual (la herencia múltiple interactúa especialmente mal con el recolector de basura, porque requiere mantener los punteros en el medio del objeto (al comienzo de la base) )

Y al diseñar C #, creo que analizaron Java, vieron que la herencia múltiple completa no se perdió mucho y eligieron mantener las cosas simples también.

¿Cómo resuelve C ++ la ambigüedad de firmas de métodos idénticas heredadas de múltiples clases base?

Lo hace no . Existe una sintaxis para llamar al método de clase base desde una base específica explícitamente, pero no hay forma de anular solo uno de los métodos virtuales y si no anula el método en la subclase, no es posible llamarlo sin especificar la base clase.

¿Y por qué el mismo diseño no está incorporado en C #?

No hay nada que incorporar.


Como Giorgio mencionó los métodos de extensión de interfaz en los comentarios, explicaré qué son los mixins y cómo se implementan en varios idiomas.

Las interfaces en Java y C # se limitan solo a los métodos de declaración. Pero los métodos deben implementarse en cada clase que hereda la interfaz. Sin embargo, existe una gran clase de interfaces, donde sería útil proporcionar implementaciones predeterminadas de algunos métodos en términos de otros. El ejemplo común es comparable (en 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; }
};

La diferencia con la clase completa es que esto no puede contener ningún miembro de datos. Hay varias opciones para implementar esto. Obviamente, la herencia múltiple es una. Pero la herencia múltiple es bastante complicada de implementar. Pero no es realmente necesario aquí. En cambio, muchos idiomas implementan esto al dividir el mixin en una interfaz, que es implementada por la clase y un repositorio de implementaciones de métodos, que se inyectan en la clase o se genera una clase base intermedia y se colocan allí. Esto se implementa en Ruby y D , se implementará en Java 8 y se puede implementar manualmente en C ++ utilizando el patrón de plantilla curiosamente recurrente . Lo anterior, en forma CRTP, se ve así:

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(); }
    ...
};

y se usa como:

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

Esto no requiere que nada se declare virtual como lo haría la clase base regular, por lo que si la interfaz se usa en plantillas deja abiertas opciones útiles de optimización. Tenga en cuenta que en C ++ esto probablemente todavía se heredaría como segundo padre, pero en lenguajes que no permiten herencia múltiple se inserta en la cadena de herencia única, por lo que es más como

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

La implementación del compilador puede o no evitar el envío virtual.

Se seleccionó una implementación diferente en C #. En C #, las implementaciones son métodos estáticos de clase completamente separada y la sintaxis de llamada al método es interpretada adecuadamente por el compilador si no existe un método de nombre dado, pero se define un "método de extensión". Esto tiene la ventaja de que los métodos de extensión se pueden agregar a la clase ya compilada y la desventaja de que dichos métodos no se pueden anular, por ejemplo, para proporcionar una versión optimizada.

Jan Hudec
fuente
Cabe mencionar que se introducirá herencia múltiple en Java 8 con métodos de extensión de interfaz.
Giorgio
@Giorgio: No, la herencia múltiple definitivamente no se introducirá en Java. Mixins lo será, lo cual es algo muy diferente, aunque cubre muchas razones restantes para usar herencia múltiple y la mayoría de las razones para usar un patrón de plantilla curiosamente recurrente (CRTP) y funciona principalmente como CRTP, no como herencia múltiple en absoluto.
Jan Hudec
Creo que la herencia múltiple no requiere punteros en el medio de un objeto. Si lo hiciera, la herencia de múltiples interfaces también lo requeriría.
svick
@svick: No, no lo hace. Pero la alternativa es mucho menos eficiente, ya que requiere un despacho virtual para el acceso de los miembros.
Jan Hudec
+1 para una respuesta mucho más completa que la mía.
Nathan C. Tresch
2

La respuesta es que no funciona correctamente en C ++ en caso de conflictos de espacio de nombres. Mira esto . Para evitar conflictos de espacio de nombres, debe hacer todo tipo de giros con punteros. Trabajé en MS en el equipo de Visual Studio y, al menos en parte, la razón por la que desarrollaron delegación fue para evitar la colisión del espacio de nombres por completo. Previamente, había dicho que también consideraban que las Interfaces eran parte de la solución de herencia múltiple, pero me equivoqué. Las interfaces son realmente sorprendentes y pueden hacerse funcionar en C ++, FWIW.

La delegación aborda específicamente la colisión del espacio de nombres: puede delegar en 5 clases y las 5 exportarán sus métodos a su alcance como miembros de primera clase. En el exterior mirando en este ES herencia múltiple.

Nathan C. Tresch
fuente
1
Me parece muy poco probable que esta sea la razón principal para no incluir MI en C #. Además, esta publicación no responde a la pregunta de por qué MI funciona en C ++ cuando no tiene "conflictos de espacio de nombres".
Doc Brown
@DocBrown Trabajé en MS en el equipo de Visual Studio y le aseguro que esa es al menos en parte la razón por la que desarrollaron delegación e interfaces, para evitar la colisión del espacio de nombres por completo. En cuanto a la calidad de la pregunta, meh. Use su voto negativo, otros parecen pensar que es útil.
Nathan C. Tresch
1
No tengo intención de rechazar su respuesta, ya que creo que es correcta. Pero su respuesta pretende que MI es completamente inutilizable en C ++, y esa es la razón por la que no lo introdujeron en C #. Aunque no me gusta mucho el MI en C ++, creo que no es completamente inadmisible.
Doc Brown
1
Es una de las razones, y no quise dar a entender que nunca funciona. Cuando algo no es determinista en informática, tiendo a decir que está "roto" o que "no funciona" cuando intento decir "es como el vudú, puede o no funcionar, encender las velas y rezar". : D
Nathan C. Tresch
1
¿son las "colisiones de espacio de nombres" no deterministas?
Doc Brown