2014-09-04 12 views
10

Używam C++ 11 i g ++ 4.8 na Ubuntu Trusty.Czy można wywołać "usuwanie destruktora" czystej klasy wirtualnej?

rozważyć następujący fragment

class Parent { 
public: 
    virtual ~Parent() = default; 
    virtual void f() = 0; 
}; 

class Child: public Parent { 
public: 
    void f(){} 
}; 

Called użyciu

{ 
    Child o; 
    o.f(); 
} 
{ 
    Parent * o = new Child; 
    delete o; 
} 
{ 
    Child * o = new Child; 
    delete o; 
} 

używam gcov do generowania mój raport pokrycia kodu. Informuje, że destruktor o symbolu _ZN6ParentD0Ev nigdy nie jest wywoływany, podczas gdy _ZN6ParentD2Ev jest.

Odpowiedź Dual emission of constructor symbols i GNU GCC (g++): Why does it generate multiple dtors? informuje, że usuwanym konstruktorem jest _ZN6ParentD0Ev.

Czy jest jakiś przypadek, w którym to "usuwanie destruktora" jest wywoływane w klasie Parent?

Pytanie uzupełniające: jeśli nie, czy istnieje sposób na uzyskanie narzędzia pokrycia kodu gcov/lcov (używanego po odpowiedzi na Detailed guide on using gcov with CMake/CDash?) zignorować ten symbol w swoim raporcie?

+0

Tak więc odpowiedź brzmi "nie, nie ma sposobu, aby uzyskać zasięg tej funkcji?" – RPGillespie

+0

Czy kiedykolwiek zastanawiałeś się, jak zmusić gcov do zignorowania tego symbolu? – RPGillespie

+0

Jeśli dobrze pamiętam, po prostu zignorowałem zasięg destruktora, używając standardowych komentarzy GCOV o strukturze – rcomblen

Odpowiedz

6

Myślę, że to dlatego, że masz obiekt Child, a nie Parent.

{ 
    Child o; 
    o.f(); 
} // 1 

{ 
    Parent * o = new Child; 
    delete o; 
} // 2 

{ 
    Child * o = new Child; 
    delete o; 
} // 3 

W // 1, o zostaje zniszczona, a kompletny obiekt destructor z Child nazywa. Ponieważ Child dziedziczy Parent, będzie wywoływał obiekt bazowy destruktor, który jest _ZN6ParentD2Ev, z Parent.

W // 2, o jest dynamicznie przydzielane i usunięte, a destructor usuwanie z Child nazywa. Następnie wywoła obiekt bazowy destruktor obiektu z Parent. W obu wywoływany jest obiekt podstawowy destruktora.

// 3 jest to samo. jest równy // 2, z wyjątkiem typu o.


Przetestowałem go na Cygwin & g ++ 4.8.3 & Windows 7 x86 SP1. Oto mój kod testowy.

class Parent 
{ 
public: 
    virtual ~Parent() { } 
    virtual void f() = 0; 
}; 

class Child : public Parent 
{ 
public: 
    void f() { } 
}; 

int main() 
{ 
    { 
     Child o; 
     o.f(); 
    } 
    { 
     Parent * o = new Child; 
     delete o; 
    } 
    { 
     Child * o = new Child; 
     delete o; 
    } 
} 

i skompilować opcja & gcov:

$ g++ -std=c++11 -fprofile-arcs -ftest-coverage -O0 test.cpp -o test 
$ ./test 
$ gcov -b -f test.cpp 

Oto wynik.

 -: 0:Source:test.cpp 
     -: 0:Graph:test.gcno 
     -: 0:Data:test.gcda 
     -: 0:Runs:1 
     -: 0:Programs:1 
function _ZN6ParentC2Ev called 2 returned 100% blocks executed 100% 
     2: 1:class Parent 
     -: 2:{ 
     -: 3:public: 
function _ZN6ParentD0Ev called 0 returned 0% blocks executed 0% 
function _ZN6ParentD1Ev called 0 returned 0% blocks executed 0% 
function _ZN6ParentD2Ev called 3 returned 100% blocks executed 75% 
     3: 4: virtual ~Parent() = default; 
call 0 never executed 
call 1 never executed 
branch 2 never executed 
branch 3 never executed 
call 4 never executed 
branch 5 taken 0% (fallthrough) 
branch 6 taken 100% 
call 7 never executed 
     -: 5: virtual void f() = 0; 
     -: 6:}; 
     -: 7: 
function _ZN5ChildD0Ev called 2 returned 100% blocks executed 100% 
function _ZN5ChildD1Ev called 3 returned 100% blocks executed 75% 
function _ZN5ChildC1Ev called 2 returned 100% blocks executed 100% 
     7: 8:class Child : public Parent 
call 0 returned 100% 
call 1 returned 100% 
call 2 returned 100% 
branch 3 taken 0% (fallthrough) 
branch 4 taken 100% 
call 5 never executed 
call 6 returned 100% 
     -: 9:{ 
     -: 10:public: 
function _ZN5Child1fEv called 1 returned 100% blocks executed 100% 
     1: 11: void f() { } 
     -: 12:}; 
     -: 13: 
function main called 1 returned 100% blocks executed 100% 
     1: 14:int main() 
     -: 15:{ 
     -: 16: { 
     1: 17:  Child o; 
     1: 18:  o.f(); 
call 0 returned 100% 
call 1 returned 100% 
     -: 19: } 
     -: 20: { 
     1: 21:  Parent * o = new Child; 
call 0 returned 100% 
call 1 returned 100% 
     1: 22:  delete o; 
branch 0 taken 100% (fallthrough) 
branch 1 taken 0% 
call 2 returned 100% 
     -: 23: } 
     -: 24: { 
     1: 25:  Child * o = new Child; 
call 0 returned 100% 
call 1 returned 100% 
     1: 26:  delete o; 
branch 0 taken 100% (fallthrough) 
branch 1 taken 0% 
call 2 returned 100% 
     -: 27: } 
     1: 28:} 

Jak widać, _ZN6ParentD2Ev The destructur przedmiot podstawy Base, nazywa się natomiast inni Base nie są nazywane.

Jednak _ZN5ChildD0Ev, usuwanie destruktora Child, nazywany jest dwa razy i _ZN5ChildD1Ev, kompletny obiekt destruktora Child, nazywa się trzy razy, ponieważ nie delete o; i Child o;.

Ale według mojego wyjaśnienia, _ZN5ChildD0Ev powinna nazywać się dwukrotnie i _ZN5ChildD1Ev powinien być nazywany raz, to nie powinien? Aby dowiedzieć się powód, zrobiłem to:

$ objdump -d test > test.dmp 

Wynik:

00403c88 <__ZN5ChildD0Ev>: 
    403c88: 55      push %ebp 
    403c89: 89 e5     mov %esp,%ebp 
    403c8b: 83 ec 18    sub $0x18,%esp 
    403c8e: a1 20 80 40 00   mov 0x408020,%eax 
    403c93: 8b 15 24 80 40 00  mov 0x408024,%edx 
    403c99: 83 c0 01    add $0x1,%eax 
    403c9c: 83 d2 00    adc $0x0,%edx 
    403c9f: a3 20 80 40 00   mov %eax,0x408020 
    403ca4: 89 15 24 80 40 00  mov %edx,0x408024 
    403caa: 8b 45 08    mov 0x8(%ebp),%eax 
    403cad: 89 04 24    mov %eax,(%esp) 
    403cb0: e8 47 00 00 00   call 403cfc <__ZN5ChildD1Ev> 
    403cb5: a1 28 80 40 00   mov 0x408028,%eax 
    403cba: 8b 15 2c 80 40 00  mov 0x40802c,%edx 
    403cc0: 83 c0 01    add $0x1,%eax 
    403cc3: 83 d2 00    adc $0x0,%edx 
    403cc6: a3 28 80 40 00   mov %eax,0x408028 
    403ccb: 89 15 2c 80 40 00  mov %edx,0x40802c 
    403cd1: 8b 45 08    mov 0x8(%ebp),%eax 
    403cd4: 89 04 24    mov %eax,(%esp) 
    403cd7: e8 a4 f9 ff ff   call 403680 <___wrap__ZdlPv> 
    403cdc: a1 30 80 40 00   mov 0x408030,%eax 
    403ce1: 8b 15 34 80 40 00  mov 0x408034,%edx 
    403ce7: 83 c0 01    add $0x1,%eax 
    403cea: 83 d2 00    adc $0x0,%edx 
    403ced: a3 30 80 40 00   mov %eax,0x408030 
    403cf2: 89 15 34 80 40 00  mov %edx,0x408034 
    403cf8: c9      leave 
    403cf9: c3      ret  
    403cfa: 90      nop 
    403cfb: 90      nop 

Tak, ponieważ _ZN5ChildD0Ev rozmowy _ZN5ChildD1Ev, _ZN5ChildD1Ev nazwano trzy razy. (1 + 2) Domyślam się, że to tylko implementacja GCC - w celu zmniejszenia duplikacji.

+0

Czy oznacza to, że podczas usuwania obiektu, jedyne wywoływane "usuwanie destruktora" jest jedynym z ostatecznego/rzeczywistego typu, a nie inne z hierarchii dziedziczenia? Jeśli tak, to oczywiście nigdy nie zostanie wywołany 'Parent'. – rcomblen

+0

@rcomblen * Oczywiście *. – ikh

1

Nie możesz mieć obiektów nadrzędnych, więc nie. To nadzór GCC, że ta niepotrzebna funkcja jest generowana. Optymalizator powinien go usunąć, ponieważ jest nieużywany, ale odkryłem, że problem z GCC ma również ten obszar.

0

Jak wyjaśnił Ikh, destruktor D0 jest niepotrzebnie generowany (i bezużyteczny), gdy czysta wirtualna klasa nadrzędna ma wirtualny destruktor.

Jeśli jednak czysta wirtualna klasa dominująca posiada non-wirtualny destruktora można usunąć wskaźnik do typu macierzystego i ten będzie powołać rodzica D0 destruktora. Oczywiście nie-wirtualne destruktory w klasie nadrzędnej rzadko są pożądane lub zamierzone, więc g ++ wysyła ostrzeżenie: [-Wdelete-non-virtual-dtor].

Powiązane problemy