Question

Je remarque parfois des programmes qui plantent sur mon ordinateur avec l'erreur suivante: "Appel de fonction virtuelle pure".

Comment ces programmes sont-ils même compilés lorsqu'un objet ne peut pas être créé à partir d'une classe abstraite?

Était-ce utile?

La solution

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

(Voir la démonstration en direct ici )

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
}

Autres conseils

En plus du cas standard d'appel d'une fonction virtuelle depuis le constructeur ou le destructeur d'un objet avec des fonctions virtuelles pures, vous pouvez également obtenir un appel de fonction virtuelle pure (au moins sur MSVC) si vous appelez une fonction virtuelle après l'objet a été détruit. Évidemment, c'est une très mauvaise chose à faire, mais si vous utilisez des classes abstraites comme interfaces et que vous vous trompez, c'est quelque chose que vous pourriez voir. C'est probablement plus probable si vous utilisez des interfaces référencées et comptées et que vous avez un bogue ref count ou si vous avez une condition de concurrence d'objet / destruction d'objet dans un programme multithread ... Le problème avec ce type de purecall est que c'est Il est souvent moins facile de comprendre ce qui se passe, car une vérification des "suspects habituels" d'appels virtuels chez ctor et dtor sera vérifiée.

Pour vous aider à résoudre ce type de problèmes, vous pouvez, dans différentes versions de MSVC, remplacer le gestionnaire purecall de la bibliothèque d'exécution. Vous faites cela en fournissant votre propre fonction avec cette signature:

int __cdecl _purecall(void)

et en le liant 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 s'est passé; 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).

Habituellement, lorsque vous appelez une fonction virtuelle via un pointeur suspendu, l'instance a probablement déjà été détruite.

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

J'imagine qu'un vtbl a été créé pour la classe abstraite pour une raison interne (il peut être nécessaire pour une sorte d'informations de type à l'exécution) et quelque chose ne va pas et un objet réel l'obtient. C'est un bug. Cela seul devrait indiquer que quelque chose qui ne peut pas arriver est.

Pure spéculation

modifier: , je me trompe dans le cas en question. OTOH IIRC certaines langues autorisent les appels vtbl à partir du constructeur destructor.

J'utilise VS2010 et chaque fois que j'essaie d'appeler un destructeur directement à partir d'une méthode publique, un "appel de fonction virtuelle pure" est généré. erreur lors de l'exécution.

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

public:
  void SomeMethod1() { this->~Foo(); }; /* ERROR */
};

J'ai donc déplacé le contenu de ~ Foo () à l'intérieur d'une 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 */
};

Si vous utilisez Borland / CodeGear / Embarcadero / Idera C ++ Builder, vous pouvez simplement implémenter

extern "C" void _RTLENTRY _pure_error_()
{
    //_ErrorExit("Pure virtual function called");
    throw Exception("Pure virtual function called");
}

Pendant le débogage, placez un point d'arrêt dans le code et consultez la pile d'appels dans l'EDI. Sinon, enregistrez la pile d'appels dans votre gestionnaire d'exceptions (ou cette fonction) si vous disposez des outils appropriés à cette fin. Personnellement, j'utilise MadExcept pour cela.

PS. L'appel de fonction d'origine est dans [C ++ Builder] \ source \ cpprtl \ Source \ misc \ pureerr.cpp

J'ai rencontré le scénario selon lequel les fonctions virtuelles pures sont appelées à cause d'objets détruits. Len Holgate a déjà une très bonne réponse. J'aimerais bien  ajouter de la couleur avec un exemple:

  1. Un objet dérivé est créé et le pointeur (en tant que classe de base) est enregistré quelque part
  2. L'objet dérivé est supprimé, mais le pointeur est en quelque sorte toujours référencé
  3. Le pointeur qui pointe vers supprimé Dérivé l'objet s'appelle

Le destructeur de la classe dérivée réinitialise les points vptr sur la classe de base vtable, qui a la fonction virtuelle pure. Ainsi, lorsque nous appelons la fonction virtuelle, elle appelle en réalité les fonctions virtuelles pures.

Cela peut être dû à un bogue de code évident ou à un scénario compliqué de conditions de concurrence critique dans les environnements multithreads.

Voici un exemple simple (compiler g ++ avec optimisation désactivée - un programme simple pourrait facilement être optimisé):

 #include <iostream>
 using namespace std;

 char pool[256];

 struct Base
 {
     virtual void foo() = 0;
     virtual ~Base(){};
 };

 struct Derived: public Base
 {
     virtual void foo() override { cout <<"Derived::foo()" << endl;}
 };

 int main()
 {
     auto* pd = new (pool) Derived();
     Base* pb = pd;
     pd->~Derived();
     pb->foo();
 }

Et la trace de la pile ressemble à:

#0  0x00007ffff7499428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff749b02a in __GI_abort () at abort.c:89
#2  0x00007ffff7ad78f7 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7adda46 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007ffff7adda81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007ffff7ade84f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x0000000000400f82 in main () at purev.C:22

Mettez en surbrillance:

si l'objet est complètement supprimé, ce qui signifie que le destructeur est appelé et que memroy est récupéré, nous pouvons simplement obtenir un défaut de segmentation car la mémoire est revenue dans le système d'exploitation, et le programme peut simplement ' t y accéder. Donc, cet " appel de fonction virtuelle pure " Le scénario se produit généralement lorsque l’objet est alloué sur le pool de mémoire, tandis qu’un objet est supprimé, la mémoire sous-jacente n’est en fait pas récupérée par le système d’exploitation, il est toujours accessible par le processus.

Voici un moyen sournois pour que cela se produise. Cela m’arrive essentiellement à 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();
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top