Domanda

A volte noto programmi che si bloccano sul mio computer con l'errore:"chiamata di funzione virtuale pura".

Come si compilano questi programmi anche quando non è possibile creare un oggetto di una classe astratta?

È stato utile?

Soluzione

Possono risultare se si tenta di effettuare una chiamata di funzione virtuale da un costruttore o un distruttore.Poiché non è possibile effettuare una chiamata di funzione virtuale da un costruttore o distruttore (l'oggetto della classe derivata non è stato costruito o è già stato distrutto), chiama la versione della classe base, che nel caso di una funzione virtuale pura, non non esiste.

(Vedi demo dal vivo Qui)

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
}

Altri suggerimenti

Oltre al caso standard di chiamare una funzione virtuale dal costruttore o dal distruttore di un oggetto con funzioni virtuali pure, puoi anche ottenere una chiamata di funzione virtuale pura (almeno su MSVC) se chiami una funzione virtuale dopo che l'oggetto è stato distrutto .Ovviamente questa è una cosa piuttosto brutta da provare a fare, ma se stai lavorando con classi astratte come interfacce e sbagli, allora è qualcosa che potresti vedere.È probabilmente più probabile se stai utilizzando interfacce conteggiate con riferimenti e hai un bug nel conteggio dei riferimenti o se hai una condizione di competizione per l'uso/distruzione di oggetti in un programma multi-thread...Il problema di questi tipi di purecall è che spesso è meno facile capire cosa sta succedendo poiché un controllo per i "soliti sospetti" delle chiamate virtuali in ctor e dtor risulterà pulito.

Per facilitare il debug di questo tipo di problemi è possibile, in varie versioni di MSVC, sostituire il gestore purecall della libreria di runtime.Puoi farlo fornendo la tua funzione con questa firma:

int __cdecl _purecall(void)

e collegandolo prima di collegare la libreria runtime.Questo ti dà il controllo su cosa succede quando viene rilevata una purecall.Una volta che hai il controllo puoi fare qualcosa di più utile del gestore standard.Ho un gestore che può fornire una traccia dello stack di dove è avvenuta la purecall;Vedere qui: http://www.lenholgate.com/blog/2006/01/purecall.html per ulteriori dettagli.

(Nota che puoi anche chiamare _set_purecall_handler() per installare il tuo gestore in alcune versioni di MSVC).

Di solito quando chiami una funzione virtuale tramite un puntatore pendente, molto probabilmente l'istanza è già stata distrutta.

Ci possono essere anche ragioni più "creative":forse sei riuscito a tagliare la parte del tuo oggetto in cui è stata implementata la funzione virtuale.Ma di solito è solo che l'istanza è già stata distrutta.

Immagino che ci sia un vtbl creato per la classe astratta per qualche motivo interno (potrebbe essere necessario per una sorta di informazioni sul tipo di runtime) e qualcosa va storto e un oggetto reale lo ottiene.E' un bug.Questo da solo dovrebbe dire che qualcosa che non può accadere lo è.

Pura speculazione

modificare: sembra che mi sia sbagliato nel caso in questione.OTOH IIRC alcuni linguaggi consentono chiamate vtbl dal costruttore distruttore.

Utilizzo VS2010 e ogni volta che provo a chiamare il distruttore direttamente dal metodo pubblico, ricevo un errore di "chiamata di funzione virtuale pura" durante il runtime.

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

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

Quindi ho spostato ciò che c'è dentro ~Foo() per separare il metodo privato, quindi ha funzionato a meraviglia.

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

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

Se usi Borland/CodeGear/Embarcadero/Idera C++ Builder, puoi semplicemente implementare

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

Durante il debug posiziona un punto di interruzione nel codice e visualizza lo stack di chiamate nell'IDE, altrimenti registra lo stack di chiamate nel gestore delle eccezioni (o in quella funzione) se disponi degli strumenti appropriati per questo.Personalmente utilizzo MadExcept per questo.

PS.La chiamata di funzione originale è in [C++ Builder]\source\cpprtl\Source\misc\pureerr.cpp

Mi sono imbattuto nello scenario in cui le funzioni virtuali pure vengono chiamate a causa di oggetti distrutti, Len Holgate Ho già una risposta molto bella, vorrei aggiungere un po 'di colore con un esempio:

  1. Viene creato un oggetto derivato e il puntatore (come classe base) viene salvato da qualche parte
  2. L'oggetto derivato viene eliminato, ma in qualche modo il puntatore è ancora referenziato
  3. Viene chiamato il puntatore che indica l'oggetto derivato eliminato

Il distruttore della classe derivata reimposta i punti vptr sulla classe base vtable, che ha la funzione virtuale pura, quindi quando chiamiamo la funzione virtuale, in realtà richiama quelle virtuali pure.

Ciò potrebbe accadere a causa di un evidente bug del codice o di uno scenario complicato di condizioni di competizione in ambienti multi-threading.

Ecco un semplice esempio (compilazione g++ con l'ottimizzazione disattivata: un semplice programma potrebbe essere facilmente ottimizzato):

 #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();
 }

E l'analisi dello stack è simile a:

#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

Evidenziare:

se l'oggetto viene completamente eliminato, il che significa che viene chiamato il distruttore e memroy viene recuperato, potremmo semplicemente ottenere un Segmentation fault poiché la memoria è tornata al sistema operativo e il programma non riesce ad accedervi.Quindi questo scenario di "chiamata di funzione virtuale pura" di solito si verifica quando l'oggetto viene allocato nel pool di memoria, mentre un oggetto viene eliminato, la memoria sottostante non viene effettivamente recuperata dal sistema operativo, è ancora accessibile dal processo.

Ecco un modo subdolo perché ciò accada.Fondamentalmente mi è successo questo oggi.

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();
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top