¿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 #? (Conozco el problema de diamantes , pero no es lo que estoy preguntando 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 #?

    
pregunta Sandeep 30.01.2013 - 06:17

2 respuestas

24
  

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

Creo (sin tener una referencia difícil), 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 herencia múltiple es a menudo demasiado complejo para su propio bien que no. 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 centro del objeto (al comienzo de la base) )

Y cuando diseñaron C # creo que miraron a 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 varias 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 manera de anular solo uno de los métodos virtuales y, si no reemplaza el método en la subclase, no es posible llamarlo sin especificar la base clase.

  

¿Y por qué no se incorpora el mismo diseño 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 # están limitadas a los métodos de declaración solamente. Pero los métodos deben implementarse en cada clase que herede la interfaz. Sin embargo, hay 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-lenguaje):

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 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 lenguajes implementan esto dividiendo la mezcla en una interfaz, que es implementada por la clase y un repositorio de implementaciones de métodos, que se inyectan en la propia 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 patrón de plantilla con cierto carácter recurrente . Lo anterior, en forma de 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 las opciones de optimización útiles. Tenga en cuenta que, en C ++, esto probablemente todavía se heredaría como segundo padre, pero en los idiomas que no permiten la 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 clases completamente separadas y la sintaxis de llamada de método es interpretada apropiadamente 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 tales métodos no se pueden anular, por ejemplo. para proporcionar la versión optimizada.

    
respondido por el Jan Hudec 30.01.2013 - 09:20
2

La respuesta es que no funciona correctamente en C ++ en caso de conflicto de espacio de nombres. Consulte esto . Para evitar choques de espacio de nombres, tienes que 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 la delegación fue para evitar la colisión del espacio de nombres por completo. Previamente, dije que también consideraban que las interfaces formaban parte de la solución de herencia múltiple, pero me equivoqué. Las interfaces son realmente sorprendentes y se pueden hacer 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 esta es herencia múltiple.

    
respondido por el Nathan C. Tresch 30.01.2013 - 06:37

Lea otras preguntas en las etiquetas