Frage

Ich merke manchmal Programme, den Absturz auf meinem Computer mit dem Fehler: „rein virtueller Funktionsaufruf“

.

Wie kompilieren diese Programme auch dann, wenn ein Objekt nicht von einer abstrakten Klasse erstellt werden?

War es hilfreich?

Lösung

Sie können, wenn Sie versuchen, eine virtuelle Funktion Anruf von einem Konstruktor oder Destruktor zu machen. Da Sie nicht eine virtuelle Funktion Anruf von einem Konstruktor oder Destruktor (die abgeleitete Klasse Objekt nicht aufgebaut worden ist oder bereits zerstört wurde) machen kann, ruft es die Version der Basisklasse, die im Falle einer rein virtuellen Funktion, doesn ‚t vorhanden sein.

(Siehe Live-Demo hier )

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
}

Andere Tipps

Neben der Standardfall eine virtuelle Funktion aus dem Konstruktor oder Destruktor eines Objekts mit rein virtuellen Funktionen aufrufen Sie auch eine rein virtueller Funktionsaufruf (auf MSVC mindestens) erhalten, wenn Sie eine virtuelle Funktion nach dem Objekt aufrufen wurde zerstört. Offensichtlich ist dies eine ziemlich schlechte Sache, um zu versuchen und zu tun, aber wenn Sie mit abstrakten Klassen als Schnittstellen arbeiten und vermasseln, dann ist es etwas, das Sie sehen könnte. Es ist vielleicht wahrscheinlicher, wenn Sie verwiesen gezählt Schnittstellen verwenden, und Sie haben einen Fehler Verweiszähler oder wenn Sie ein Objekt Verwendung / Objekt Zerstörung Race-Bedingung in einem multi-threaded Programm haben ... Die Sache über diese Art von purecall ist, dass es oft weniger leicht als Kontrolle vor sich geht zu ergründen, was für die ‚üblichen Verdächtigen‘ der virtuellen Anrufe in ctor und dtor wird kommen sauber.

mit helfen, diese Art von Problemen Debuggen können Sie in verschiedenen Versionen von MSVC, ersetzen Sie die purecall Handler-Laufzeitbibliothek. Sie tun dies, indem Sie Ihre eigene Funktion mit dieser Signatur bereitstellt:

int __cdecl _purecall(void)

und die Verknüpfung, bevor Sie die Laufzeitbibliothek verknüpfen. Dies gibt Ihnen die Kontrolle über was passiert, wenn ein purecall erkannt wird. Sobald Sie die Kontrolle haben, können Sie etwas nützlicher als die Standard-Handler tun. Ich habe einen Handler, der einen Stack-Trace, wo der purecall geschah zur Verfügung stellen kann; siehe hier: http://www.lenholgate.com/blog/2006/01/ purecall.html für weitere Details.

(Beachten Sie auch anrufen _set_purecall_handler können () Handler in einigen Versionen von MSVC zu installieren).

Normalerweise, wenn Sie eine virtuelle Funktion durch einen baumelnden Zeiger nennen -. Höchstwahrscheinlich die Instanz bereits zerstört worden

Es kann mehr „kreative“ Gründe sein, auch: vielleicht haben Sie es geschafft haben, den Teil des Objekts abschneiden, wo die virtuelle Funktion implementiert wurde. Aber in der Regel ist es nur, dass die Instanz bereits zerstört worden ist.

Ich würde vermuten, es eine vtbl für die abstrakte Klasse für einigen internen Grund geschaffen ist (es könnte für irgendeine Art von Laufzeittypinformationen erforderlich) etwas schief geht und ein reales Objekt bekommt es. Es ist ein Fehler. Das allein sollte sagen, dass etwas, das nicht passieren kann, ist.

Reine Spekulation

Bearbeiten sieht aus wie ich in dem Fall in Frage falsch bin. OTOH IIRC einige Sprachen tun vtbl Anrufe aus dem Konstruktor destructor ermöglichen.

Ich benutze VS2010 und wenn es destructor direkt aus öffentlicher Methode versuche Aufruf, erhalte ich einen „rein virtuellen Funktionsaufruf“ Fehler während der Laufzeit.

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

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

Also zog ich, was drin ist ~ Foo () private Methode zu trennen, dann ist es wie ein Charme.

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

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

Wenn Sie Borland / CodeGear / Embarcadero / Idera C ++ Builder verwenden, können Sie Ihre nur implementieren

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

Während Ort Debuggen einen Haltepunkt in dem Code und sehen Sie die Aufrufliste in der IDE, melden Sie sich sonst den Call-Stack in Ihrem Exception-Handler (oder die Funktion), wenn Sie die entsprechenden Tools dafür. Ich persönlich benutze MadExcept dafür.

PS. Der ursprüngliche Funktionsaufruf ist in [C ++ Builder] \ source \ cpprtl \ Source \ misc \ pureerr.cpp

ich in das Szenario lief, dass die rein virtuellen Funktionen, da zerstörte Objekte aufgerufen werden, Len Holgate hat bereits eine sehr schöne Antwort, würde Ich mag  etwas Farbe mit einem Beispiel hinzuzufügen:

  1. ein abgeleitetes Objekt wird erstellt und der Zeiger (als Basisklasse) ist gespeichert irgendwo
  2. Die abgeleitete Objekt wird gelöscht, aber irgendwie ist der Zeiger noch referenziert
  3. Der Zeiger, der gelöscht Abgeleitet verweist auf Objekt wird aufgerufen

Die abgeleitete Klasse destructor die vptr zeigt auf die Basisklasse V-Tabelle zurückzusetzen, die die rein virtuelle Funktion hat, so dass, wenn wir die virtuelle Funktion aufrufen, ruft sie tatsächlich in die reinen virutal diejenigen.

Das wegen einer offensichtlichen Code Fehler passieren könnte, oder ein kompliziertes Szenario der Race-Bedingung in Multithreading-Umgebungen.

Hier ist ein einfaches Beispiel (g ++ mit Optimierung kompiliert ausgeschaltet - ein einfaches Programm leicht wegoptimiert werden könnte):

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

Und der Stack-Trace wie folgt aussieht:

#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

Highlight:

, wenn das Objekt vollständig gelöscht wird, wird das heißt destructor genannt, und memroy zurückgewonnen wird, können wir einfach ein Segmentation fault erhalten, wie der Speicher an das Betriebssystem zurückgegeben hat, und das Programm kann nicht darauf zugreifen. So dass dieses „rein virtueller Funktionsaufruf“ Szenario in der Regel passiert, wenn das Objekt auf dem Speicherpool zugeordnet ist, während ein Objekt gelöscht wird, wird die zugrunde liegenden Speicher tatsächlich nicht von O freigegeben, es durch das Verfahren nach wie vor dort zugänglich ist.

Hier ist eine hinterhältige Art und Weise für sie passieren. Ich hatte mir dies im Wesentlichen heute geschehen.

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();
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top