2008-09-19 33 views

Répondre

92

Ils peuvent résulter si vous essayez d'effectuer un appel de fonction virtuelle à partir d'un constructeur ou d'un destructeur. Puisque vous ne pouvez pas faire un appel de fonction virtuelle à partir d'un constructeur ou d'un destructeur (l'objet de classe dérivé n'a pas été construit ou a déjà été détruit), il appelle la version de classe de base, qui dans le cas d'une fonction virtuelle pure. n'existe pas.

(Voir la démo en direct here)

class Base 
{ 
public: 
    Base() { doIt(); } // DON'T DO THIS 
    virtual void doIt() = 0; 
}; 

void Base::doIt() 
{ 
    std::cout<<"Is it fine to call pure virtual function from constructor?"; 
} 

class Derived : public Base 
{ 
    void doIt() {} 
}; 

int main(void) 
{ 
    Derived d; // This will cause "pure virtual function call" error 
} 
+2

Une raison pour laquelle le compilateur n'a pas pu attraper cela, en général? – Thomas

+0

Je ne vois aucune raison technique pour laquelle le compilateur n'a pas pu l'attraper. –

+1

GCC me donne un avertissement seulement: test.cpp: Dans le constructeur 'Base :: Base()': test.cpp: 4: avertissement: virtuel virtuel 'base vide :: doIt()' appelé du constructeur Mais il échoue au moment de la liaison. – Thomas

0

Je suppose qu'il existe un vtbl créé pour la classe abstraite pour une raison interne (il peut être nécessaire pour une sorte d'info sur le type d'exécution) et que quelque chose se passe mal et qu'un objet réel l'obtienne. C'est un bug. Cela seul devrait dire que quelque chose qui ne peut pas arriver est.

Pure spéculation

modifier: semble que je me trompe dans le cas en question. OTOH IIRC certaines langues permettent aux appels vtbl de sortir du constructeur destructeur.

+0

Ce n'est pas un bogue dans le compilateur, si c'est ce que vous voulez dire. – Thomas

+0

Votre soupçon est juste - C# et Java le permettent. Dans ces langues, les bohjects en construction ont leur type final. En C++, les objets changent de type pendant la construction et c'est pourquoi et quand vous pouvez avoir des objets avec un type abstrait. – MSalters

+0

* Toutes les classes abstraites, et les objets réels créés à partir de celles-ci, ont besoin d'un vtbl (table de fonction virtuelle), énumérant les fonctions virtuelles qui devraient y être appelées. En C++, un objet est responsable de la création de ses propres membres, y compris la table de fonction virtuelle. Les constructeurs sont appelés de la classe de base à la classe dérivée, et les destructeurs sont appelés de la classe dérivée à la classe de base, donc dans une classe de base abstraite, la table de fonction virtuelle n'est pas encore disponible. – fuzzyTew

7

Habituellement, lorsque vous appelez une fonction virtuelle via un pointeur ballants - très probablement l'instance a déjà été détruite.

Il peut y avoir aussi des raisons plus «créatives»: peut-être avez-vous réussi à découper la partie de votre objet où la fonction virtuelle a été implémentée. Mais généralement, c'est juste que l'instance a déjà été détruite.

-1

Voici une manière sournoise pour que cela se produise. Je l'avais essentiellement pour moi aujourd'hui.

class A 
{ 
    A *pThis; 
    public: 
    A() 
    : pThis(this) 
    { 
    } 

    void callFoo() 
    { 
    pThis->foo(); // call through the pThis ptr which was initialized in the constructor 
    } 

    virtual void foo() = 0; 
}; 

class B : public A 
{ 
public: 
    virtual void foo() 
    { 
    } 
}; 

B b(); 
b.callFoo(); 
+1

Au moins, il ne peut pas être reproduit sur mon vc2008, le vptr pointe vers A vtable quand initialisé dans le constructeur A, mais quand B est complètement initialisé, le vptr est changé pour pointer vers vtable de B, ce qui est ok –

+0

coudnt reproduisez-le soit avec vs2010/12 – makc

+0

* 'Je l'avais essentiellement pour l'instant '* évidemment pas vrai, car tout simplement faux: une fonction virtuelle pure est appelée seulement quand' callFoo() 'est appelée dans un constructeur (ou destructeur) , parce qu'à ce moment l'objet est encore (ou déjà) à un stade. [Voici une version en cours] (https://ideone.com/5zi4Kc) de votre code sans l'erreur de syntaxe dans 'B b();' - les parenthèses en font une déclaration de fonction, vous voulez un objet. – Wolf

60

En plus le cas standard d'appeler une fonction virtuelle du constructeur ou destructeur d'un objet avec des fonctions virtuelles pures vous pouvez également obtenir un appel de fonction virtuelle pure (sur MSVC au moins) si vous appelez un virtuel fonction après que l'objet a été détruit. Évidemment, c'est une très mauvaise chose à faire, mais si vous travaillez avec des classes abstraites en tant qu'interfaces et que vous vous trompez, c'est quelque chose que vous pourriez voir. C'est probablement plus probable si vous utilisez des interfaces comptées référencées et si vous avez un bug de décompte de référence ou si vous avez une condition de course de destruction d'objet/objet dans un programme multithread ... La chose à propos de ces types de purecall est qu'il est souvent moins facile de comprendre ce qui se passe comme un chèque pour les «suspects habituels» des appels virtuels dans ctor et dtor sera propre.

Pour faciliter le débogage de ces types de problèmes, vous pouvez, dans différentes versions de MSVC, remplacer le gestionnaire purecall de la bibliothèque d'exécution. Vous le faites en fournissant votre propre fonction avec cette signature:

int __cdecl _purecall(void) 

et de le lier avant de lier la bibliothèque d'exécution. Cela vous donne le contrôle de ce qui se passe quand un purecall est détecté. Une fois que vous avez le contrôle, vous pouvez faire quelque chose de plus utile que le gestionnaire standard. J'ai un gestionnaire qui peut fournir une trace de pile de l'endroit où le purecall est arrivé; voir ici: http://www.lenholgate.com/blog/2006/01/purecall.html pour plus de détails.

(Notez que vous pouvez également appeler _set_purecall_handler() pour installer votre gestionnaire dans certaines versions de MSVC).

+0

Merci pour le pointeur sur l'obtention d'un appel _purecall() sur une instance supprimée; Je n'étais pas au courant de cela, mais je l'ai prouvé avec un petit code de test. En regardant un vidage post-mortem dans WinDbg, je pensais avoir affaire à une course où un autre thread essayait d'utiliser un objet dérivé avant qu'il ne soit complètement construit, mais cela éclaire le problème et semble mieux correspondre à la preuve. –

+0

Une autre chose que j'ajouterai: l'appel '_purecall()' qui se produit normalement lors de l'appel d'une méthode d'une instance supprimée _not_ se produit si la classe de base a été déclarée avec l'optimisation '__declspec (novtable)' (spécifique à Microsoft) . Avec cela, il est tout à fait possible d'appeler une méthode virtuelle surchargée après que l'objet a été supprimé, ce qui pourrait masquer le problème jusqu'à ce qu'il vous mette sous une autre forme. Le piège '_purecall()' est votre ami! –

+0

C'est utile de savoir Dave, j'ai récemment vu quelques situations où je n'étais pas purecalls quand je pensais que je devrais être. Peut-être que j'étais en train de tomber sur cette optimisation. –

0

J'utilise VS2010 et chaque fois que j'essaie d'appeler destructor directement à partir de la méthode publique, j'obtiens une erreur "pure virtual function call" pendant l'exécution. J'ai donc déplacé ce qui est à l'intérieur de ~ Foo() pour séparer la méthode privée, puis cela a fonctionné comme un charme.

template <typename T> 
class Foo { 
public: 
    Foo<T>() {}; 
    ~Foo<T>() {}; 

public: 
    void _MethodThatDestructs() {}; 
    void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */ 
};