Ein sehr häufiger Fall: Der Versuch, eine rein virtuelle Methode aus dem Konstruktor aufzurufen ...
Konstrukteurs
struct Interface
{
Interface();
virtual void logInit() const = 0;
};
struct Concrete: Interface()
{
virtual void logInit() const { std::cout << "Concrete" << std::endl; }
};
Nun nehmen wir die folgende Implementierung von Interface()
Interface::Interface() {}
Dann ist alles in Ordnung:
Concrete myConcrete;
myConcrete.pure(); // outputs "Concrete"
Es ist so ein Schmerz rein nach dem Konstruktor zu nennen, wäre es besser, den Code richtig zu faktorisieren?
Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;)
Dann können wir es in einer Linie tun !!
Concrete myConcrete; // CRASHES VIOLENTLY
Warum?
Da das Objekt von unten nach oben gebaut wird. Lass es uns anschauen.
Anleitung eine Concrete
Klasse (grob)
Weisen Sie ausreichend Speicher (natürlich), und genug Speicher für die _vtable zu (1 Funktionszeiger pro virtuelle Funktion, in der Regel in der Reihenfolge bauen sie deklariert sind von links nach rechts Basis ausgehend)
Anruf Concrete
Konstruktor (der Code, den Sie nicht sehen)
a>Interface
Konstruktoraufruf, die die _vtabl initialisieren e mit seinem Zeiger
b>Interface
Konstruktor Körper Anruf (Sie schrieb, dass)
c> die Zeiger in der _vtable Überschreibung Beton außer Kraft setzen für diesen Methoden Concrete
Konstruktor Körper
d> Anrufe (Sie schrieb, dass)
Also, was ist das Problem? Nun, schauen Sie sich b>
und c>
bestellen;)
Wenn Sie eine virtual
Methode aus einem Konstruktor aufrufen, tut es nicht, was Sie erhoffen. Es geht an die _vtable, um den Zeiger nachzuschlagen, aber die _vtable
ist noch nicht vollständig initialisiert. Also, für alles, was zählt, ist die Wirkung von:
D() { this->call(); }
ist in der Tat:
D() { this->D::call(); }
Wenn eine virtuelle Methode aus einem Konstruktor aufrufen, müssen Sie nicht die volle dynamische Art des Objekts Wenn Sie einen Build erstellen, wird der statische Typ des aktuellen Konstruktors aufgerufen.
In meinem Interface
/Concrete
Beispiel bedeutet es Interface
Typ, und das Verfahren ist virtuell rein, so wird der _vtable keinen echten Zeiger (0x0 oder 0x01 zum Beispiel halten, wenn Ihr Compiler ist freundlich genug, um Setup-Debug-Werte helfe dir da).
Destructors
Coincidently, lassen Sie uns den Destructor Fall untersuchen;)
struct Interface { ~Interface(); virtual void logClose() const = 0; }
Interface::~Interface() { this->logClose(); }
struct Concrete { ~Concrete(); virtual void logClose() const; char* m_data; }
Concrete::~Concrete() { delete[] m_data; } // It's all about being clean
void Concrete::logClose()
{
std::cout << "Concrete refering to " << m_data << std::endl;
}
Also, was auf die Zerstörung geschieht? Nun, die _vtable funktioniert gut, und der echte Laufzeittyp wird aufgerufen ... Was es hier bedeutet ist jedoch undefiniertes Verhalten, denn wer weiß, was mit m_data
passiert ist, nachdem es gelöscht wurde und bevor Interface
destructor aufgerufen wurde?Ich weiß nicht;)
Fazit
Nie virtuelle Methoden aus Konstrukteuren oder Destruktoren aufrufen.
Wenn es nicht das ist, sind Sie mit einem Speicher Korruption links, Pech;)
Können Sie klären, ob der Funktionszeiger in der VTable 0 ist oder wenn es die diese Zeiger, die null ist? Oder ist es ein Zeiger im Objekt, der auf die aktuelle vtable verweist? –
Der Vtable-Zeiger ist 1. Der Absturz tritt auf, wenn versucht wird, 'call * 0x1c (% eax)' (in AT & T-Syntax) und der Wert von EAX ist 1 (nicht Null, wie ich falsch angegeben). – Tobias
Führen Sie es in Gdb, legen Sie einen Überwachungspunkt an die Adresse, sehen Sie, was es schreibt. –