Środowisko programistyczne: GNU GCC (g ++) 4.1.2
Podczas gdy próbuję zbadać, jak zwiększyć `` pokrycie kodu - szczególnie pokrycie funkcji '' w testach jednostkowych, odkryłem, że niektóre klasy dtor wydają się być generowane wiele razy. Czy ktoś z was ma pojęcie, dlaczego, proszę?
Wypróbowałem i zaobserwowałem to, o czym wspomniałem powyżej, używając następującego kodu.
W „test.h”
class BaseClass
{
public:
~BaseClass();
void someMethod();
};
class DerivedClass : public BaseClass
{
public:
virtual ~DerivedClass();
virtual void someMethod();
};
W „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;
}
Kiedy zbudowałem powyższy kod (g ++ test.cpp -o test), a następnie zobaczyłem, jakie symbole zostały wygenerowane w następujący sposób,
nm --demangle test
Widziałem następujący wynik.
==== 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()
Moje pytania są następujące.
1) Dlaczego wygenerowano wielu lekarzy (BaseClass - 2, DerivedClass - 3)?
2) Jaka jest różnica między tymi lekarzami? Jak tych wielu lekarzy będzie selektywnie wykorzystywanych?
Mam teraz wrażenie, że aby osiągnąć 100% pokrycie funkcji projektu C ++, musielibyśmy to zrozumieć, aby móc wywołać wszystkich tych lekarzy w moich testach jednostkowych.
Byłbym bardzo wdzięczny, gdyby ktoś mógł udzielić mi odpowiedzi na powyższe.
źródło
Odpowiedzi:
Po pierwsze, cele tych funkcji są opisane w Itanium C ++ ABI ; zobacz definicje w sekcji „podstawowy destruktor obiektów”, „kompletny niszczyciel obiektów” i „usuwający niszczyciel”. Odwzorowanie na zniekształcone nazwy podano w 5.1.4.
Gruntownie:
operator delete
aby faktycznie zwolnić pamięć.Jeśli nie masz wirtualnych klas podstawowych, D2 i D1 są identyczne; GCC, na wystarczających poziomach optymalizacji, faktycznie aliasuje symbole do tego samego kodu dla obu.
źródło
struct B: virtual A
a następniestruct C: B
, kiedy niszczącB
inwokujesz,B::D1
które z kolei inwokujesz,A::D2
a podczas niszczeniaC
inwokujesz,C::D1
które inwokująB::D2
iA::D2
(zwróć uwagę, jakB::D2
nie wywołuje niszczyciela A). To, co naprawdę jest niesamowite w tym podziale, to faktyczna możliwość zarządzania wszystkimi sytuacjami za pomocą prostej liniowej hierarchii 3 destruktorów.Zwykle istnieją dwa warianty konstruktora ( nie-odpowiedzialny / odpowiedzialny ) i trzy-destruktor ( nie-odpowiedzialny / odpowiedzialny / odpowiedzialny za usuwanie ).
Nie-in-charge konstruktor i dtor są wykorzystywane podczas pracy obiektu klasy, która dziedziczy z innej klasy za pomocą
virtual
słowa kluczowego, gdy obiekt nie znajduje się pełna przedmiot (tak obecny obiekt nie jest „za” konstruowania lub niszczącej wirtualny obiekt bazowy). Ten ctor otrzymuje wskaźnik do wirtualnego obiektu podstawowego i przechowuje go.W firmę konstruktor i dtors są dla wszystkich innych przypadkach, to znaczy gdy nie ma dziedziczenia wirtualnego zaangażowany; jeśli klasa ma wirtualny destruktor The usuwanie w firmę wskaźnik dtor idzie do vtable gnieździe, natomiast zakres, który zna typ dynamicznego obiektu (czyli dla obiektów z automatycznym lub statyczny okres przechowywania) użyje w firmę dtor (ponieważ ta pamięć nie powinna zostać zwolniona).
Przykład kodu:
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);
Wyniki:
foo
,baz
iquux
punktem, w odpowiednim do naładowania znosi dtor.b1
ib2
są zbudowane przezbaz()
kierownika , który wzywafoo(1)
do zarządzaniaq1
iq2
są zbudowane przez osobęquux()
odpowiedzialną , która jestfoo(2)
odpowiedzialna ibaz()
nie jest odpowiedzialna za pomocą wskaźnika dofoo
obiektu, który skonstruował wcześniejq2
jest niszczony przez~auto_ptr()
in-charge , który wywołuje~quux()
usuwanie wirtualnego dtora odpowiedzialnego za usunięcie , które wywołuje~baz()
nie-odpowiedzialny ,~foo()
odpowiedzialny ioperator delete
.q1
jest niszczony przez osobę~quux()
odpowiedzialną , która wywołuje~baz()
brak władzy i~foo()
odpowiedzialnośćb2
jest niszczony przez~auto_ptr()
in-charge , który wywołuje~baz()
usuwanie wirtualnego dtora odpowiedzialnego za usuwanie , które wywołuje~foo()
in-charge ioperator delete
b1
jest niszczony przez osobę~baz()
odpowiedzialną , która wywołuje~foo()
kontrolęKażdy, kto wywodzi się z programu,
quux
użyłby swojego niebędącego zarządcą i dtora i wziąłby na siebie odpowiedzialność za stworzeniefoo
obiektu.W zasadzie wariant nie -odpowiedzialny nigdy nie jest potrzebny dla klasy, która nie ma wirtualnych baz; w takim przypadku wariant odpowiedzialny jest czasami nazywany zunifikowanym i / lub symbole zarówno dla odpowiedzialnego, jak i nie odpowiedzialnego są aliasowane do jednej implementacji.
źródło
delete
wyrażenia albo jako część własnego destruktora, albo jako część wywołań destruktora podobiektu.delete
Ekspresja jest realizowany albo jako wezwanie przez vtable obiektu, jeśli ma destruktor wirtualny (gdzie znajdziemy usunięcia w firmę , lub jako bezpośrednie wywołanie obiektu w firmę destructor.delete
Wyrażenie nie wywołuje nie-w-charge wariant, który jest używany tylko przez inne destruktorów podczas niszczenia obiektu, który wykorzystuje wirtualny dziedziczenia.