virtuales ventajas públicos puras protegidos programacion privados polimorfismo multiple herencia funciones funcion elementos desventajas c++ multiple-inheritance virtual-functions diamond-problem

c++ - ventajas - polimorfismo



Herencia múltiple+función desorden virtual (6)

A menos que sobrescriba fn nuevamente en D , no, no es posible. Debido a que no hay un anulador final en un objeto D: tanto C como B anulan A::fn . Tienes varias opciones:

  • Suelte C::fn o B::fn . Entonces, el que todavía anula A::fn tiene el anulador final.
  • Coloque un anulador final en D. Luego, aquel anula A::fn y fn en C y B

Por ejemplo, los siguientes resultados en un error de tiempo de compilación:

#include <iostream> class A { public: virtual void fn() { } }; class B : public virtual A { public: virtual void fn() { } }; class C : public virtual A { public: virtual void fn() { } }; // does not override fn!! class D : public B, public C { public: virtual void doit() { B::fn(); C::fn(); } }; int main(int argc, char **argv) { D d; d.doit(); return 0; }

Sin embargo, puede derivar no virtual de A en C y B, pero entonces ya no tiene herencia de diamante. Es decir, cada miembro de datos en A aparece dos veces en B y C porque tiene dos subobjetos de clase base A en un objeto D. Te recomendaría repensar ese diseño. Intenta eliminar objetos dobles como los que requieren herencia virtual. A menudo causa este tipo de situaciones conflictivas.

Un caso muy similar a esto es cuando desea anular una función específica. Imagina que tienes una función virtual con el mismo nombre en B y C (ahora sin una base A común). Y en D, desea anular cada función pero dar un comportamiento diferente a cada una. Dependiendo de si llama a la función con un puntero B o un puntero C, tiene el comportamiento diferente. Herencia múltiple La Parte III de Herb Sutter describe una buena manera de hacerlo. Podría ayudarte a decidir tu diseño.

Tengo un escenario de herencia múltiple de diamante como este:

A / / B C / / D

El padre común, A, define una función virtual fn ().
¿Es posible que tanto B como C definan fn() ?
Si es así, entonces la siguiente pregunta es: ¿puede D acceder a Fn () de B y C sin desambiguación? Supongo que hay alguna sintaxis para esto ...
¿Y es posible que D haga eso sin saber específicamente quiénes son B y C? B y C pueden ser reemplazados por otras clases y quiero que el código en D sea genérico.

Lo que estoy tratando de hacer es tener D de alguna manera enumerar todas las instancias de fn () que tiene en su ascendencia. ¿Es esto posible en algún otro medio que las funciones virtuales?


Es posible que desee ver Loki TypeLists si realmente necesita poder rastrear la ascendencia y enumerar los tipos. No estoy seguro si lo que estás pidiendo es realmente posible sin un montón de trabajo. Asegúrate de que no estás sobre ingeniería aquí.

En una nota ligeramente diferente, si va a utilizar MI de esta manera (es decir, el temido diamante ), entonces debe ser muy explícito sobre qué miembro virtual desea. No puedo pensar en un buen caso en el que quiera elegir la semántica de B::fn() sobre C::fn() sin tomar una decisión explícita al escribir D Probablemente elija uno sobre el otro (o incluso ambos) según lo que haga el método individual. Una vez que haya tomado una decisión, el requisito es que los cambios heredados no cambien las expectativas o la interfaz semántica.

Si está realmente preocupado por intercambiar una nueva clase, diga E en lugar de B donde E no desciende de B sino que ofrece la misma interfaz, entonces realmente debería usar el enfoque de plantilla, aunque no estoy seguro de por qué hay una static_cast<> allí ...

struct A { virtual ~A() {} virtual void f() = 0; }; struct B: A { virtual void f() { std::cout << "B::f()" << std::endl; } }; struct C: A { virtual void f() { std::cout << "C::f()" << std::endl; } }; template <typename Base1, typename Base2> struct D: Base1, Base2 { void g() { Base1::f(); Base2::f(); } }; int main() { D<B,C> d1; D<C,B> d2; d1.g(); d2.g(); return 0; } // Outputs: // B::f() // C::f() // C::f() // B::f()

Funciona bien y parece un poco más fácil de ver.


No puede enumerar las definiciones de fn () en la ascendencia. C ++ carece de reflexión. La única forma en que puedo imaginarlo es un bucle gigante que prueba el tipo de todos los antepasados ​​posibles. Y duele imaginar eso.


Primera pregunta, sí, B y C pueden definir fn() como una función virtual. Segundo, D, por supuesto, puede acceder a B::fn() y C::fn() usando el operador de alcance :: Tercera pregunta: D debe saber al menos B y C, ya que debe definirlos en la lista de herencia. Puedes usar plantillas para permitir que los tipos de B y C se abran:

class A { public: virtual ~A() {} virtual void fn() = 0; }; class B: public A { public: virtual ~B() {} virtual void fn(){ std::cout << "B::fn()" << std::endl; } }; class C: public A { public: virtual ~C() {} virtual void fn(){ std::cout << "C::fn()" << std::endl; } }; template <typename TypeB, typename TypeC> class D: public TypeB, public TypeC { public: void Do() { static_cast<TypeB*>(this)->fn(); static_cast<TypeC*>(this)->fn(); } }; typedef D<B, C> DInst; DInst d; d.Do();

Acerca del deseo de enumerar automáticamente todas las funciones fn () de todas las clases de las que D hereda: No estoy seguro de que sea posible sin tener que recurrir a MPL. Al menos puede ampliar mi ejemplo anterior con versiones que se ocupan de 3 y más parámetros de plantilla, pero supongo que hay un límite superior (compilador interno) del número de parámetros de plantilla de clase.


Vividos ya ha contestado la parte principal del post. Incluso si usaría el operador de alcance en lugar del operador de desreferencia static_cast <> + más incómodo.

Dependiendo de la tarea en cuestión, tal vez pueda cambiar la relación de herencia de D a B y C para obtener una composición de acoplamiento menos (más la herencia de A). Esto es asumiendo que no necesita que D sea usado polimórficamente como B o C, y que realmente no requiere que B y C compartan la misma instancia base.

Si opta por la composición, puede recibir B y C como argumentos para su constructor como referencias / punteros de tipo A, lo que hace que D ignore por completo los tipos B y C. En ese punto, puede usar un contenedor para contener tantos A los objetos derivados. Su propia implementación de fn () (si así lo decide) o cualquier otro método.