Entwicklungsumgebung: GNU GCC (g ++) 4.1.2
Während ich versuche zu untersuchen, wie die Codeabdeckung - insbesondere die Funktionsabdeckung - beim Komponententest erhöht werden kann, habe ich festgestellt, dass ein Teil der Klasse dtor anscheinend mehrmals generiert wird. Haben einige von Ihnen eine Idee, warum, bitte?
Ich habe versucht und beobachtet, was ich oben erwähnt habe, indem ich den folgenden Code verwendet habe.
In "test.h"
class BaseClass
{
public:
~BaseClass();
void someMethod();
};
class DerivedClass : public BaseClass
{
public:
virtual ~DerivedClass();
virtual void someMethod();
};
In "test.cpp"
#include <iostream>
#include "test.h"
BaseClass::~BaseClass()
{
std::cout << "BaseClass dtor invoked" << std::endl;
}
void BaseClass::someMethod()
{
std::cout << "Base class method" << std::endl;
}
DerivedClass::~DerivedClass()
{
std::cout << "DerivedClass dtor invoked" << std::endl;
}
void DerivedClass::someMethod()
{
std::cout << "Derived class method" << std::endl;
}
int main()
{
BaseClass* b_ptr = new BaseClass;
b_ptr->someMethod();
delete b_ptr;
}
Wenn ich den obigen Code erstellt habe (g ++ test.cpp -o test) und dann sehe, welche Art von Symbolen wie folgt generiert wurden:
nm - Entwirrungstest
Ich konnte die folgende Ausgabe sehen.
==== following is partial output ====
08048816 T DerivedClass::someMethod()
08048922 T DerivedClass::~DerivedClass()
080489aa T DerivedClass::~DerivedClass()
08048a32 T DerivedClass::~DerivedClass()
08048842 T BaseClass::someMethod()
0804886e T BaseClass::~BaseClass()
080488f6 T BaseClass::~BaseClass()
Meine Fragen lauten wie folgt.
1) Warum wurden mehrere Dtoren generiert (BaseClass - 2, DerivedClass - 3)?
2) Was ist der Unterschied zwischen diesen Dtoren? Wie werden diese mehreren Dtoren selektiv verwendet?
Ich habe jetzt das Gefühl, dass wir dies verstehen müssen, um eine 100% ige Funktionsabdeckung für C ++ - Projekte zu erreichen, damit ich alle diese Dtoren in meinen Komponententests aufrufen kann.
Ich würde mich sehr freuen, wenn mir jemand die Antwort auf das oben Gesagte geben könnte.
quelle
Antworten:
Zunächst werden die Zwecke dieser Funktionen im Itanium C ++ ABI beschrieben . Siehe Definitionen unter "Basisobjekt-Destruktor", "Vollständiger Objekt-Destruktor" und "Löschen des Destruktors". Die Zuordnung zu verstümmelten Namen ist in 5.1.4 angegeben.
Grundsätzlich:
operator delete
auf, um den Speicher tatsächlich freizugeben.Wenn Sie keine virtuellen Basisklassen haben, sind D2 und D1 identisch. GCC wird bei ausreichenden Optimierungsstufen die Symbole tatsächlich für beide auf denselben Code aliasen.
quelle
struct B: virtual A
und dann , wenn Sie einenstruct C: B
zerstörenB
, rufen Sie auf,B::D1
was wiederum aufruft,A::D2
und wenn Sie einen zerstörenC
, rufen Sie auf,C::D1
was aufruftB::D2
undA::D2
(beachten Sie, wieB::D2
kein Destruktor aufgerufen wird). Was in dieser Unterteilung wirklich erstaunlich ist, ist, tatsächlich alle Situationen mit einer einfachen linearen Hierarchie von 3 Destruktoren verwalten zu können.Es gibt normalerweise zwei Varianten des Konstruktors ( nicht verantwortlich / verantwortlich ) und drei des Destruktors ( nicht verantwortlich / verantwortlich / verantwortlich löschen ).
Der nicht verantwortliche ctor und dtor werden verwendet, wenn ein Objekt einer Klasse, die von einer anderen Klasse erbt, mit dem
virtual
Schlüsselwort behandelt wird, wenn das Objekt nicht das vollständige Objekt ist (das aktuelle Objekt ist also "nicht verantwortlich" für das Konstruieren oder Zerstören das virtuelle Basisobjekt). Dieser ctor empfängt einen Zeiger auf das virtuelle Basisobjekt und speichert es.Der verantwortliche ctor und dtors gelten für alle anderen Fälle, dh wenn keine virtuelle Vererbung vorliegt; Wenn die Klasse über einen virtuellen Destruktor verfügt, wird der zuständige Lösch-dtor-Zeiger in den vtable-Slot verschoben, während ein Bereich, der den dynamischen Typ des Objekts kennt (dh für Objekte mit automatischer oder statischer Speicherdauer), den verantwortlichen dtor verwendet (weil dieser Speicher nicht freigegeben werden sollte).
Codebeispiel:
Ergebnisse:
foo
,baz
undquux
Punkt , an den jeweiligen in-charge Löschen dtor.b1
undb2
werden vonbaz()
Verantwortlichen konstruiert , diefoo(1)
Verantwortliche anrufenq1
undq2
werden von einemquux()
Verantwortlichen konstruiert , der mit einem Zeiger auf das zuvor konstruierte Objektfoo(2)
verantwortlich undbaz()
nicht verantwortlich istfoo
q2
wird durch den~auto_ptr()
Verantwortlichen zerstört , der das~quux()
Löschen des virtuellen Verantwortlichen aufruft, der das~baz()
Nicht-Verantwortliche , das~foo()
Verantwortliche und das Aufrufen aufruftoperator delete
.q1
wird durch~quux()
Verantwortliche zerstört , die~baz()
Nicht-Verantwortliche und~foo()
Verantwortliche anrufenb2
wird zerstört durch~auto_ptr()
in-charge , die die virtuelle dtor ruft~baz()
in-charge Löschen , die Anrufe~foo()
in-Gebühr undoperator delete
b1
wird durch destructed~baz()
in-charge , die Anrufe~foo()
in-GebührJeder, der von stammt,
quux
würde seinen nicht verantwortlichen ctor und dtor verwenden und die Verantwortung für die Erstellung desfoo
Objekts übernehmen.Im Prinzip wird die nicht verantwortliche Variante niemals für eine Klasse benötigt, die keine virtuellen Basen hat. In diesem Fall wird die verantwortliche Variante manchmal als einheitlich bezeichnet , und / oder die Symbole für sowohl verantwortlich als auch nicht verantwortlich sind auf eine einzelne Implementierung ausgerichtet.
quelle
delete
Ausdruck entweder als Teil Ihres eigenen Destruktors oder als Teil der Unterobjekt-Destruktoraufrufe erhalten. Derdelete
Ausdruck wird entweder als Aufruf über die vtable des Objekts implementiert, wenn er über einen virtuellen Destruktor verfügt (wo wir das Löschen von Verantwortlichen finden , oder als direkter Aufruf an den verantwortlichen Destruktor des Objekts)delete
Ausdruck ruft niemals die nicht verantwortliche Variante auf, die nur von anderen Destruktoren verwendet wird, während ein Objekt zerstört wird, das virtuelle Vererbung verwendet.