2014-09-04 2 views
10

Я использую C++ 11 и g ++ 4.8 в Ubuntu Trusty.Есть ли способ вызвать «удаление деструктора» из чистого виртуального класса?

Рассмотрим фрагмент

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

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

Вызывается с помощью

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

Я использую gcov генерировать свой отчет покрытия кода. Сообщается, что деструктор с символом _ZN6ParentD0Ev никогда не вызывается, а _ZN6ParentD2Ev есть.

Ответ Dual emission of constructor symbols и GNU GCC (g++): Why does it generate multiple dtors? сообщает, что _ZN6ParentD0Ev является конструктором удаления.

Есть ли случаи, когда этот «удаляющий деструктор» вызывается на классе Parent?

Вспомогательный вопрос: если нет, есть ли способ получить средство покрытия кода gcov/lcov (используется следующий ответ Detailed guide on using gcov with CMake/CDash?) игнорировать этот символ в своем отчете?

+0

Так ответ «нет, нет способа получить освещение этой функции?» – RPGillespie

+0

Вы когда-нибудь выясняли, как заставить gcov игнорировать этот символ? – RPGillespie

+0

Если я хорошо помню, я просто проигнорировал покрытие деструктора, используя стандартные структурированные комментарии GCOV – rcomblen

ответ

6

Я думаю, это потому, что у вас есть объект Child, а не Parent объект.

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

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

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

В // 1, o разрушается, и полный объект деструктор из Child называется. Так как Child наследует Parent, он назовет деструктор базового объекта, который является _ZN6ParentD2Ev, of Parent.

В // 2, o динамически выделяется и удаляется, а удаление Деструктор из Child называется. Затем он вызовет деструктор базового объекта Parent. В обоих называется деструктор базового объекта.

// 3 такой же. это всего лишь // 2, за исключением o.


Я испытал это на Cygwin & г ++ 4.8.3 & Windows 7 x86 SP1. Вот мой тестовый код.

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

и компилировать вариант & gcov:

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

Вот результат.

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

Как вы можете видеть, _ZN6ParentD2Ev, базовый объект destructur из Base, называется в то время как остальные Base не называются.

Однако _ZN5ChildD0Ev, удаление Деструктор Child, вызывается дважды и _ZN5ChildD1Ev, полный объект деструктор Child, называется три раза, так как есть delete o; и Child o;.

Но согласно мое объяснение, _ZN5ChildD0Ev следует назвать дважды и _ZN5ChildD1Ev следует назвать раз, должны не так ли? Для того, чтобы выяснить причину, я сделал это:

$ objdump -d test > test.dmp 

Результат:

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 

Да, так _ZN5ChildD0Ev звонки _ZN5ChildD1Ev, _ZN5ChildD1Ev был назван трижды. (1 + 2) Я предполагаю, что это просто реализация GCC - для уменьшения дублирования.

+0

Означает ли это, что при удалении объекта единственный вызывающий «уничтожающий деструктор» - это один из конечного/фактического типа, а не любой другой из иерархии наследства? Если это так, то, очевидно, он никогда не будет вызван на «Родительский». – rcomblen

+0

@rcomblen * Конечно *. – ikh

1

У вас не может быть родительских объектов, поэтому нет. Это контроль GCC, что эта ненужная функция генерируется. Оптимизатор действительно должен удалить его, поскольку он не используется, но я обнаружил, что GCC имеет проблемы и в этой области.

0

Как пояснил ikh, деструктор D0 бесполезно генерируется (и неприменим), когда чистый виртуальный родительский класс имеет виртуальный деструктор.

Однако, если чистый виртуальный родительский класс имеет невиртуальный деструктор, вы можете удалить указатель на родительский тип, и это будет вызывать Д0 деструктор родителя. Конечно, не виртуальные деструкторы в родительском классе редко желательны или предназначены, поэтому g ++ испускает предупреждение: [-Wdelete-non-virtual-dtor].

Смежные вопросы