2010-01-20 10 views
7

Considere esta situación simple:C++ no se llama en la subclase

A.h 

class A { 
public: 
    virtual void a() = 0; 
}; 

B.h 

#include <iostream> 

class B { 
public: 
    virtual void b() {std::cout << "b()." << std::endl;}; 
}; 

C.h 

#include "A.h" 
#include "B.h" 

class C : public B, public A { 
public: 
    void a() {std::cout << "a() in C." << std::endl;}; 
}; 

int main() { 
    B* b = new C(); 
    ((A*) b)->a(); // Output: b(). 

    A* a = new C(); 
    a->a();   // Output:: a() in C. 

    return 0; 
} 

En otras palabras:
- A es una clase virtual pura .
- B es una clase sin superclase y una función virtual no pura.
- C es una subclase de A y B e invalida la función virtual pura de A.

Lo que me sorprende es la primera salida es decir,

((A*) b)->a(); // Output: b(). 

Aunque yo llamo una() en el código, b() se invoca. Supongo que está relacionado con el hecho de que la variable b es un puntero a la clase B que no es una subclase de la clase A. Pero aún así el tipo de tiempo de ejecución es un puntero a una instancia de C.

¿Cuál es la regla exacta de C++ para explicar esto, desde un punto de vista de Java, comportamiento extraño?

+2

En pocas palabras: ** B no son de A ** Son totalmente sin relación entre sí, pero su (¡no es bueno usarlo!) El estilo C no le importa de ninguna manera. 'dynamic_cast' recorrerá correctamente su jerarquía. Cuando lanzas tipos de puntero no relacionados, obtienes un comportamiento indefinido. Eso significa que podría pasar cualquier cosa, desde que parezca funcionar hasta hacer explotar tu computadora. – GManNickG

+0

No te olvides de los demonios nasales. –

Respuesta

24

Usted está emitiendo incondicionalmente b a A* usando C-style cast. El compilador no te impide hacer esto; dijiste que es A*, por lo que es A*. Por lo tanto, trata la memoria a la que apunta como una instancia de A. Dado que a() es el primer método enumerado en A 's vtable y b() es el primer método enumerado en el vtable B, cuando se llama a a() en un objeto que realmente es un B, obtiene b().

Tienes suerte de que el diseño del objeto sea similar. Esto no está garantizado para ser el caso.

En primer lugar, no debe usar moldes de estilo C. Debe usar C++ casting operators que tenga más seguridad (aunque aún puede dispararse en el pie, por lo tanto, lea los documentos con cuidado).

En segundo lugar, no debe confiar en este tipo de comportamiento, a menos que use dynamic_cast<>.

+0

Puede confiar por completo en cross-casting si se usa 'dynamic_cast'. Está 100% garantizado para trabajar o le devolvemos su dinero. – coppro

+0

@coppro: si usa dynamic_cast, sí. Intentaba enfatizar que no debía confiar en que esto funcionara con el elenco estilo C. Actualicé mi última declaración para que quede más claro. –

+0

+1 para tener suerte. –

11

No utilice un molde de estilo C al lanzar a través de un árbol de herencia múltiple. Si utiliza dynamic_cast su lugar se obtiene el resultado esperado:

B* b = new C(); 
dynamic_cast<A*>(b)->a(); 
+0

Esto debería ser un error de tiempo de ejecución dentro del ejemplo dado. ¿Correcto? –

+0

@tehMick, no será un comportamiento indefinido – Trent

+0

@teh @Trent: Ambos están equivocados. : P Funciona correctamente, atravesando la jerarquía de herencia. – GManNickG

2

((A*) b) es un reparto de c-estilo explícita, que se deja sin importar lo que los tipos son señalaron. Sin embargo, si intenta desreferenciar este puntero, será un error de tiempo de ejecución o un comportamiento impredecible. Esta es una instancia de este último. La salida que observó no está de ninguna manera segura o garantizada.

5

Estás comenzando con una B * y lanzándola a A *. Dado que los dos no están relacionados, estás profundizando en la esfera del comportamiento indefinido.

0

Creo que tiene un error sutil en la conversión de B* a A*, y el comportamiento no está definido.Evite usar moldes de estilo C y prefiera los moldes de C++, en este caso dynamic_cast. Debido a la forma en que el compilador ha configurado el almacenamiento para los tipos de datos y las entradas de vtable, ha terminado encontrando la dirección de una función diferente.

1

La siguiente línea es una reinterpret_cast, que apunta en la misma memoria pero "pretende" que es un tipo diferente de objeto:

((A*) b)->a(); 

Lo que realmente quiere es un moldeado dinámico, que comprueba qué tipo de objeto b es en realidad y ajustar lo que la ubicación en la memoria para que apunte a:

dynamic_cast<A*>(b)->a() 

Como jeffamaphone mencionado, la disposición similar de las dos clases es lo que hace que la función incorrecta de ser llamado.

1

Casi nunca hay una ocasión en C++ en la que se justifique o requiera el uso de un molde de estilo C (o su equivalente en C++ reinterpret_cast <>). Cada vez que te sientas tentado de usar uno de los dos, sospecha de tu código y/o tu diseño.

2

A y B son no relacionados entre sí por medio de herencia, lo que significa que un puntero a B no se puede transformar en un puntero a A por medio de cualquiera de los dos upcast o abatido.

Desde A y B son dos bases diferentes de C, lo que está tratando de hacer aquí se denomina un -cruz fundido. El único elenco en lenguaje C++ que puede realizar un lanzamiento cruzado es dynamic_cast. Esto es lo que tiene que usar en este caso, en caso de que realmente lo necesita (¿verdad?)

B* b = new C(); 
A* a = dynamic_cast<A*>(b); 
assert(a != NULL); 
a->a();