2010-01-20 5 views
7

Considérez cette situation simple:fonction virtuelle C de pas appelé dans la sous-classe

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 d'autres termes:
- A est une classe virtuelle pure .
- B est une classe sans super classe et une fonction virtuelle non pure.
- C est une sous-classe de A et B et remplace la fonction virtuelle pure de A.

Ce qui me surprend est la première sortie à savoir

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

Bien que j'appelle() dans le code, b() est invoqué. Je suppose qu'il est lié au fait que la variable b est un pointeur vers la classe B qui n'est pas une sous-classe de la classe A. Mais le type d'exécution est toujours un pointeur vers une instance C.

Quelle est la règle C++ exacte pour expliquer cela, d'un point de vue Java, comportement bizarre?

+2

Plus simplement: ** B sont pas des A ** Ils sont totalement indépendants les uns des autres, mais votre (pas bon à utiliser!) La distribution en style C ne s'en soucie pas non plus. 'dynamic_cast' traversera correctement votre hiérarchie. Lorsque vous générez des types de pointeurs indépendants, vous obtenez un comportement indéfini. Cela signifie que tout peut arriver, de ce qui semble fonctionner pour faire exploser votre ordinateur. – GManNickG

+0

Ne pas oublier les démons nasaux. –

Répondre

24

Vous lancez inconditionnellement b à A* en utilisant un fonte de style C. Le compilateur ne vous empêche pas de faire cela; vous avez dit que c'est un A* donc c'est un A*. Donc, il traite la mémoire qu'il pointe comme une instance de A. Depuis a() est la première méthode répertoriée dans A vtable et b() est la première méthode répertoriée dans B vtable, lorsque vous appelez a() sur un objet qui est vraiment un B, vous obtenez b().

Vous êtes chanceux que la disposition des objets soit similaire. Ce n'est pas garanti d'être le cas. En premier lieu, vous ne devez pas utiliser de moules de type C. Vous devriez utiliser C++ casting operators qui ont plus de sécurité (bien que vous pouvez toujours vous tirer dans le pied, alors lisez attentivement les docs). Deuxièmement, vous ne devriez pas compter sur ce genre de comportement, à moins que vous n'utilisiez dynamic_cast<>.

+0

Vous pouvez entièrement compter sur la diffusion croisée si 'dynamic_cast' est utilisé. C'est 100% garanti au travail ou votre argent. – coppro

+0

@coppro: si vous utilisez dynamic_cast, oui. J'essayais de mettre l'accent sur le fait de ne pas compter sur ce travail avec la distribution de style C. J'ai mis à jour ma dernière déclaration pour le rendre plus clair. –

+0

+1 pour avoir de la chance. –

11

Ne pas utiliser une distribution de type C lors de la conversion d'une arborescence d'héritage multiple. Si vous utilisez dynamic_cast au lieu que vous obtenez le résultat attendu:

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

Cela devrait être une erreur d'exécution dans l'exemple donné. Correct? –

+0

@tehMick, non ce sera un comportement indéfini – Trent

+0

@teh @Trent: Vous avez tous les deux tort. : P Cela fonctionne correctement, en traversant la hiérarchie d'héritage. – GManNickG

2

((A*) b) est explicite de style c, ce qui est permis, peu importe ce que les types ont souligné sont. Cependant, si vous essayez de déréférencer ce pointeur, ce sera une erreur d'exécution ou un comportement imprévisible. Ceci est une instance de ce dernier. La sortie que vous avez observée n'est en aucun cas sûre ou garantie.

5

Vous commencez avec un B * et le moulage en A *. Puisque les deux ne sont pas liés, vous êtes plonger dans la sphère du comportement indéfini.

0

Je pense que vous avez un bug subtil dans la distribution de B* à A*, et le comportement est indéfini.Évitez d'utiliser des moules de style C et préférez les moulages C++ - dans ce cas dynamic_cast. En raison de la façon dont votre compilateur a défini le stockage pour les types de données et les entrées vtable, vous avez fini par trouver l'adresse d'une fonction différente.

1

La ligne suivante est un reinterpret_cast, qui pointe à la mémoire, mais « prétend » il est un autre type d'objet:

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

Qu'est-ce que vous voulez vraiment est un dynamic_cast, qui vérifie quel type d'objet b est vraiment et régler ce emplacement en mémoire pour pointer vers:

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

Comme jeffamaphone mentionné, la mise en page similaire des deux classes est ce qui cause la mauvaise fonction à appeler.

1

Il n'y a presque jamais d'occasion en C++ où l'utilisation d'un cast de style C (ou son équivalent C++ reinterpret_cast >) est justifiée ou requise. Chaque fois que vous êtes tenté d'utiliser l'un des deux, suspectez votre code et/ou votre design.

2

A et B sont pas liés les uns aux autres au moyen d'héritage, ce qui signifie qu'un pointeur vers B ne peut pas être transformé en un pointeur vers A au moyen soit upcast ou baissés.

Depuis A et B sont deux bases différentes de C, ce que vous essayez de faire ici est appelé un cross-cast . La seule distribution en langage C++ capable d'effectuer une conversion croisée est dynamic_cast. Voici ce que vous devez utiliser dans ce cas si vous avez vraiment besoin (avez-vous?)

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