2014-09-04 14 views
10

Sto usando C++ 11 e g ++ 4.8 su Ubuntu Trusty.C'è un modo per chiamare la "cancellazione del distruttore" di una pura classe virtuale?

consideri questo frammento

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

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

Chiamato utilizzando

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

Io uso gcov per generare il mio rapporto di copertura del codice. Segnala che il distruttore con il simbolo _ZN6ParentD0Ev non viene mai chiamato, mentre lo è _ZN6ParentD2Ev.

Risposta Dual emission of constructor symbols e GNU GCC (g++): Why does it generate multiple dtors? segnala che _ZN6ParentD0Ev è il costruttore di eliminazione.

C'è qualche caso in cui questo "eliminazione del distruttore" viene chiamato sulla classe Parent?

Domanda sussidiabile: in caso contrario, esiste un modo per ottenere lo strumento di copertura del codice gcov/lcov (utilizzato in seguito alla risposta di Detailed guide on using gcov with CMake/CDash?) ignorare tale simbolo nel suo rapporto?

+0

Quindi la risposta è "no, non c'è modo di ottenere la copertura di tale funzione?" – RPGillespie

+0

Hai mai capito come convincere gcov a ignorare quel simbolo? – RPGillespie

+0

Se ricordo bene, ho semplicemente ignorato la copertura del distruttore usando i commenti strutturati standard di GCOV – rcomblen

risposta

6

Penso che sia perché si dispone dell'oggetto Child, non dell'oggetto Parent.

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

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

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

In // 1, o viene distrutto, e il distruttore completo oggetto di Child è chiamato. Poiché Child eredita Parent, chiamerà il distruttore dell'oggetto di base , ovvero _ZN6ParentD2Ev, di Parent.

In // 2, o viene allocata dinamicamente e cancellato, e il distruttore cancellazione di Child è chiamato. Quindi chiamerà il distruttore dell'oggetto di base di Parent. In entrambi viene chiamato il distruttore dell'oggetto base.

// 3 è lo stesso. è uguale a // 2, eccetto il tipo o.


ho testato su Cygwin & g ++ 4.8.3 & Windows 7 x86 SP1. Ecco il mio codice di prova.

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; 
    } 
} 

e compilare opzione & gcov:

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

Ecco il risultato.

 -: 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:} 

Come si può vedere, _ZN6ParentD2Ev, l'oggetto di base destructur di Base, si chiama mentre gli altri di Base non sono chiamati.

Tuttavia, _ZN5ChildD0Ev, l'eliminazione di distruttore di Child, è chiamato due volte e _ZN5ChildD1Ev, completo oggetto distruttore di Child, è chiamato tre volte, dal momento che c'è delete o; e Child o;.

Ma secondo la mia spiegazione, _ZN5ChildD0Ev dovrebbe essere chiamato due volte e _ZN5ChildD1Ev dovrebbe essere chiamato volta, dovrebbe non è vero? Per capire il motivo, ho fatto questo:

$ objdump -d test > test.dmp 

Risultato:

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 

Sì, dal momento che le chiamate _ZN5ChildD0Ev_ZN5ChildD1Ev, _ZN5ChildD1Ev è stato chiamato per tre volte. (1 + 2) Immagino che sia solo l'implementazione di GCC - per ridurre la duplicazione.

+0

Significa che quando si elimina un oggetto, l'unica "cancellazione del distruttore" chiamata è quella del tipo finale/effettivo, e non qualsiasi altra dalla gerarchia ereditaria? Se è così, ovviamente non sarà mai chiamato su "Parent". – rcomblen

+0

@rcomblen * Ovviamente *. – ikh

1

Non è possibile avere oggetti padre, quindi no. È una supervisione del GCC che viene generata questa funzione inutile. L'ottimizzatore dovrebbe davvero rimuoverlo, poiché non è utilizzato, ma ho scoperto che GCC ha problemi anche in quell'area.

0

Come ha spiegato Ikh, il distruttore D0 viene generato inutilmente (e inutilizzabile) quando la classe genitore virtuale pura ha un distruttore virtuale.

Tuttavia, se la classe genitore virtuale pura ha un non virtuale distruttore , è possibile eliminare un puntatore al tipo di genitore e questo sarà richiamare D0 distruttore del genitore. Naturalmente, i distruttori non virtuali in una classe genitore sono raramente desiderabili o previsti, quindi g ++ emette l'avviso: [-Wdelete-non-virtual-dtor].