Je remarque parfois des programmes qui plantent sur mon ordinateur avec l'erreur: "appel de fonction virtuelle pure".
Comment ces programmes se compilent-ils même lorsqu'un objet ne peut pas être créé à partir d'une classe abstraite?
Je remarque parfois des programmes qui plantent sur mon ordinateur avec l'erreur: "appel de fonction virtuelle pure".
Comment ces programmes se compilent-ils même lorsqu'un objet ne peut pas être créé à partir d'une classe abstraite?
Réponses:
Ils peuvent survenir 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 la classe de base, qui dans le cas d'une fonction virtuelle pure, ne n'existe pas.
(Voir la démo 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
}
doIt()
appel dans le constructeur est facilement dévirtualisé et distribué de Base::doIt()
manière statique, ce qui provoque simplement une erreur de l'éditeur de liens. Ce dont nous avons vraiment besoin, c'est d'une situation dans laquelle le type dynamique lors d'une distribution dynamique est le type de base abstrait.
Base::Base
appelé un non-virtuel f()
qui à son tour appelle la doIt
méthode virtuelle (pure) .
En plus du cas standard de l'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 que l'objet a été détruit . De toute évidence, c'est une très mauvaise chose à essayer, mais si vous travaillez avec des classes abstraites comme interfaces et que vous vous trompez, c'est quelque chose que vous pourriez voir. C'est peut-être plus probable si vous utilisez des interfaces comptées référencées et que vous avez un bogue de comptage de références ou si vous avez une condition de concurrence d'utilisation / destruction d'objet dans un programme multithread ... Le problème avec ces types d'appels purs est que c'est il est souvent moins facile de comprendre ce qui se passe car une vérification des «suspects habituels» des appels virtuels dans ctor et dtor sera claire.
Pour vous aider à déboguer ces types de problèmes, vous pouvez, dans différentes versions de MSVC, remplacer le gestionnaire d'appels purecall de la bibliothèque d'exécution. Vous faites cela en fournissant votre propre fonction avec cette signature:
int __cdecl _purecall(void)
et le lier avant de lier la bibliothèque d'exécution. Cela VOUS donne le contrôle de ce qui se passe lorsqu'un appel 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 produit; 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).
_purecall()
invocation qui se produit normalement lors de l'appel d'une méthode d'une instance supprimée ne se produira pas si la classe de base a été déclarée avec l' __declspec(novtable)
optimisation (spécifique à Microsoft). Avec cela, il est tout à fait possible d'appeler une méthode virtuelle remplacée après la suppression de l'objet, ce qui pourrait masquer le problème jusqu'à ce qu'il vous mord sous une autre forme. Le _purecall()
piège est votre ami!
Habituellement, lorsque vous appelez une fonction virtuelle via un pointeur suspendu, l'instance a probablement 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 simplement que l'instance a déjà été détruite.
Je suis tombé sur le scénario selon lequel les fonctions virtuelles pures sont appelées à cause d'objets détruits, Len Holgate
j'ai déjà une très bonne réponse , je voudrais ajouter de la couleur avec un exemple:
Le destructeur de classe dérivée réinitialise les points vptr vers la classe de base vtable, qui a la fonction virtuelle pure, donc quand nous appelons la fonction virtuelle, il appelle en fait les fonctions virutales pures.
Cela peut se produire en raison d'un bogue de code évident ou d'un scénario compliqué de condition de concurrence dans les environnements multi-threading.
Voici un exemple simple (compile g ++ avec l'optimisation désactivée - un programme simple pourrait être facilement 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 à ceci:
#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
Surligner:
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 Segmentation fault
comme la mémoire est revenue au système d'exploitation, et le programme ne peut tout simplement pas y accéder. Donc, ce scénario "pur appel de fonction virtuelle" se produit généralement lorsque l'objet est alloué sur le pool de mémoire, alors 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, elle est toujours là accessible par le processus.
Je suppose qu'il y a un vtbl créé pour la classe abstraite pour une raison interne (il peut être nécessaire pour une sorte d'informations de type d'exécution) et que quelque chose ne va pas et qu'un objet réel l'obtient. C'est un bug. Cela seul devrait dire que quelque chose ne peut pas arriver.
Spéculation pure
edit: on dirait que je me trompe dans le cas en question. OTOH IIRC certains langages autorisent les appels vtbl hors du constructeur destructeur.
J'utilise VS2010 et chaque fois que j'essaye d'appeler destructor directement à partir de la méthode publique, j'obtiens une erreur "appel de fonction virtuelle pure" pendant l'exécution.
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void SomeMethod1() { this->~Foo(); }; /* ERROR */
};
J'ai donc déplacé ce qu'il y a à 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 */
};
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 pour cela. J'utilise personnellement MadExcept pour cela.
PS. L'appel de fonction d'origine se trouve dans [C ++ Builder] \ source \ cpprtl \ Source \ misc \ pureerr.cpp
Voici une façon sournoise pour que cela se produise. Cela m'est arrivé essentiellement 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();
I had this essentially happen to me today
évidemment pas vrai, parce que tout simplement faux: une fonction virtuelle pure n'est appelée que lorsqu'elle callFoo()
est appelée dans un constructeur (ou destructeur), car à ce moment l'objet est encore (ou déjà) au stade A. Voici une version en cours d'exécution de votre code sans l'erreur de syntaxe B b();
- les parenthèses en font une déclaration de fonction, vous voulez un objet.