15

+ función virtual Tengo un escenario herencia múltiple diamante como esto:herencia múltiple lío

A 
/ \ 
B  C 
    \ /
    D 

el padre común, A, define un fn función virtual().
¿Es posible que tanto B como C definan fn()?
Si es así, entonces la siguiente pregunta es: ¿puede D acceder tanto a B como a C fn() 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 reemplazarse por otras clases y quiero que el código en D sea genérico.

Lo que trato de hacer es tener D de alguna manera enumerar todas las instancias de fn() que tiene en su ascendencia. ¿Es esto posible de alguna otra manera que las funciones virtuales?

Respuesta

1

Ya hay varias preguntas que se ocupan de esto. Parece que nos estamos quedando sin preguntas para hacer. Tal vez el cuadro de búsqueda debería ser más grande que el botón Preguntar.

Ver

+0

No he podido encontrar una pregunta que responda a este problema específico. ¿puedes? – shoosh

+0

No es porque se trata de herencia múltiple que puede adivinar que ya se trató en otras publicaciones. Le preguntaron 'Lo que trato de hacer es que D enumere de alguna manera todas las instancias de fn() que tiene en su ascendencia. ¿Es esto posible de alguna otra manera que las funciones virtuales? '.Aunque creo que fue una pregunta algo ingenua, ninguna de las preguntas que has vinculado aquí habla de tal cosa. Creo que fue bastante específico y único en su interrogatorio. -1. –

4

Primera pregunta, sí, B y C puede definir fn() como una función virtual. En segundo lugar, D puede acceder a B::fn() y C::fn() utilizando el operador scope :: Tercera pregunta: D al menos debe saber B y C, ya que debe definirlos en la lista de herencia. Puede utilizar plantillas para dejar que los tipos de B y C abierta:

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

Sobre el deseo de enumerar de forma automática todas las funciones fn() de todas las clases que hereda de D: No estoy seguro de si eso es posible sin recurriendo a MPL. Al menos puedes extender mi ejemplo anterior con versiones que tratan con 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.

+0

Iba a publicar lo mismo. Hay un pequeño error ya que D debe heredar tanto de B como de C y eso no está en el código anterior. Otra sintaxis (más simple que el elenco) sería: TypeB :: fn() y TypeA :: fn() que parecen más naturales. –

+0

No estoy seguro si una llamada TypeB :: fn() hace lo correcto con respecto a la llamada de función virtual. Con el reparto estático, estás seguro de que tienes un objeto de tipo B. Supongo que tengo que probarlo. Gracias por la nota sobre la corrección! – vividos

+1

Esta es probablemente la solución más cercana a lo que necesito. Desafortunadamente, en mi caso, el número de clases en la herencia (B, C) también es variable. Supongo que esto tendrá que esperar los argumentos de la plantilla variable de C++ 0x. – shoosh

1

No puede enumerar las definiciones de fn() en el ancestro. C++ carece de reflejo. La única forma en que me puedo imaginar es un bucle gigante que prueba los tipos de todos los antepasados ​​posibles. Y duele imaginar eso.

0

Vividos ya ha respondido la parte principal de la publicación. Incluso si utilizara el operador de ámbito en lugar del operador static_cast <> + dereference más engorroso.

Dependiendo de la tarea en cuestión, tal vez pueda cambiar la relación de herencia de D a B y C para una composición de acoplamiento menor (más posiblemente herencia de A). Esto supone que no necesita que D se use polimórficamente como B o C, y que realmente no necesita que B y C compartan la misma instancia base.

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

18

A menos que sobrescriba fn nuevamente en D, no, no es posible. Porque no hay un overrider final en un objeto D: C y B anulan A::fn. Tiene varias opciones:

  • Caiga C::fn o B::fn. Entonces, el que todavía anula A::fn tiene el overrider final.
  • Coloque un overrider final en D. Entonces, eso anula A::fn así como fn en C y B.

Por ejemplo el siguiente resultado de 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; 
} 

Puede, sin embargo derivar no virtual de A en C y B, pero entonces no tienen herencia de diamantes más. Es decir, cada miembro de datos en A aparece dos veces en B y C porque tiene dos sub-objetos de clase base A en un objeto D. Te recomendaría que reconsiderases ese diseño. Intenta eliminar objetos dobles como ese que requieren herencia virtual. A menudo causa ese tipo de situaciones conflictivas.

Un caso muy similar a esto es cuando desea anular una función específica. Imagine que tiene una función virtual con el mismo nombre en B y C (ahora sin una base común A). Y en D desea anular cada función, pero le da un comportamiento diferente a cada una. Dependiendo de si llama a la función con un puntero B o un puntero C, tiene un comportamiento diferente. Multiple Inheritance Part III por Herb Sutter describe una buena manera de hacerlo. Puede ayudarte a decidir sobre tu diseño.

+0

Creo que eso no es lo que Shoosh quiere hacer. Él dijo "Lo que trato de hacer es que D enumere de alguna manera todas las instancias de fn() que tiene en su ascendencia". No quiere anular a fn() en la clase D. – vividos

+3

por eso dije que no es posible. si tanto C como B anulan A :: fn, entonces no puede definir D sin sobreescribir fn en D –

+0

¿Qué quisiste decir con "Dejar caer"? – yanpas

1

Es posible que desee mirar Loki TypeLists si realmente necesita para poder rastrear ascendencia y enumerar a través de los tipos. No estoy seguro si lo que estás pidiendo es realmente posible sin mucho trabajo. Asegúrate de no sobreingenierí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 desee elegir la semántica de B::fn() sobre C::fn() sin tomar una decisión explícita al escribir D. Probablemente elegirás 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 el intercambio de una nueva clase, dicen E en lugar de decir B donde E no desciende de B pero ofrece la misma interfaz, entonces usted realmente debería usar el enfoque de la plantilla, aunque no estoy seguro 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.