¿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.