Frage GNU GCC (g ++): Warum generiert es mehrere dtors?


Entwicklungsumgebung: GNU GCC (g ++) 4.1.2

Während ich versuche, zu untersuchen, wie man die Codeabdeckung - insbesondere die Funktionsabdeckung - im Komponententest erhöhen kann, habe ich festgestellt, dass ein Teil der Klasse dtor anscheinend mehrfach generiert wird. Haben einige von euch eine Idee warum?

Ich habe versucht und beobachtet, was ich oben erwähnt habe, indem ich den folgenden Code benutze.

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 wurde,

nm - Entgleisungstest

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 sind wie folgt.

1) Warum wurden mehrere Dtoren generiert (BaseClass - 2, DerivedClass - 3)?

2) Was sind die Unterschiede zwischen diesen Faktoren? Wie werden diese multiplen Dktoren selektiv verwendet?

Ich habe jetzt das Gefühl, dass wir, um 100% Funktionsabdeckung für C ++ - Projekt zu erreichen, dies verstehen müssen, damit ich all diese Dktoren in meinen Komponententests aufrufen kann.

Ich würde schätzen, wenn jemand mir die Antwort auf das oben genannte geben könnte.


76
2017-07-07 16:24


Ursprung


Antworten:


Zuerst werden die Zwecke dieser Funktionen in der Itanium C ++ ABI; Siehe Definitionen unter "base object destructor", "complete object destructor" und "destructor löschen". Die Zuordnung zu verfälschten Namen finden Sie in 5.1.4.

Grundsätzlich gilt:

  • D2 ist der "Basisobjektdestruktor". Es zerstört das Objekt selbst sowie Datenelemente und nicht virtuelle Basisklassen.
  • D1 ist der "vollständige Objektdestruktor". Es zerstört zusätzlich virtuelle Basisklassen.
  • D0 ist der "Lösch-Objektdestruktor". Es tut alles, was der komplette Objektdestruktor tut, und es ruft auf operator delete um die Erinnerung tatsächlich zu befreien.

Wenn Sie keine virtuellen Basisklassen haben, sind D2 und D1 identisch. GCC wird bei ausreichenden Optimierungsstufen die Symbole für beide in den gleichen Code umwandeln.


58
2017-07-07 17:06



Es gibt normalerweise zwei Varianten des Konstruktors (nicht verantwortlich / verantwortlich) und drei Destruktoren (nicht verantwortlich / verantwortlich / Löschen).

Das nicht verantwortlich ctor und dtor werden verwendet, wenn ein Objekt einer Klasse behandelt wird, die von einer anderen Klasse erbt virtual Schlüsselwort, wenn das Objekt nicht das vollständige Objekt ist (daher ist das aktuelle Objekt "nicht verantwortlich" für das Konstruieren oder Zerstören des virtuellen Basisobjekts). Dieser ctor erhält einen Zeiger auf das virtuelle Basisobjekt und speichert es.

Das verantwortlich ctor und dtors sind für alle anderen Fälle, d. h. wenn keine virtuelle Vererbung beteiligt ist; Wenn die Klasse einen virtuellen Destruktor hat, Löschen dtor Zeiger geht in den Vtable Slot, während ein Bereich, der den dynamischen Typ des Objekts kennt (d. h. für Objekte mit automatischer oder statischer Speicherdauer), den verantwortlich dtor (weil dieser Speicher nicht freigegeben werden sollte).

Codebeispiel:

struct foo {
    foo(int);
    virtual ~foo(void);
    int bar;
};

struct baz : virtual foo {
    baz(void);
    virtual ~baz(void);
};

struct quux : baz {
    quux(void);
    virtual ~quux(void);
};

foo::foo(int i) { bar = i; }
foo::~foo(void) { return; }

baz::baz(void) : foo(1) { return; }
baz::~baz(void) { return; }

quux::quux(void) : foo(2), baz() { return; }
quux::~quux(void) { return; }

baz b1;
std::auto_ptr<foo> b2(new baz);
quux q1;
std::auto_ptr<foo> q2(new quux);

Ergebnisse:

  • Der dtor-Eintrag in jedem der vtables für foo, baz und quux zeigen Sie auf das jeweilige Löschen dt.
  • b1 und b2 sind konstruiert von baz()  verantwortlich, die anruft foo(1)  verantwortlich
  • q1 und q2 sind konstruiert von quux()  verantwortlichwas fällt foo(2)  verantwortlich und baz()  nicht verantwortlich mit einem Zeiger auf die foo Objekt, das es früher konstruiert hat
  • q2 wird zerstört von ~auto_ptr()  verantwortlich, die den virtuellen dtor aufruft ~quux()  Löschen, die anruft ~baz()  nicht verantwortlich, ~foo()  verantwortlich und operator delete.
  • q1 wird zerstört von ~quux()  verantwortlich, die anruft ~baz()  nicht verantwortlich und ~foo()  verantwortlich
  • b2 wird zerstört von ~auto_ptr()  verantwortlich, die den virtuellen dtor aufruft ~baz()  Löschen, die anruft ~foo()  verantwortlich und operator delete
  • b1 wird zerstört von ~baz()  verantwortlich, die anruft ~foo()  verantwortlich

Jeder, der davon kommt quux würde seine benutzen nicht verantwortlich ctor und dtor und übernehmen die Verantwortung für die Erstellung der foo Objekt.

Im Prinzip, die nicht verantwortlich Variante wird nie für eine Klasse benötigt, die keine virtuellen Basen hat; in diesem Fall verantwortlich Variante heißt dann manchmal einheitlichund / oder die Symbole für beide verantwortlich und nicht verantwortlich werden auf eine einzige Implementierung umgeleitet.


33
2017-07-07 17:48