有没有办法调用纯虚拟类的“删除析构函数”?

问题描述:

我在Ubuntu Trusty上使用C++ 11和g ++ 4.8。有没有办法调用纯虚拟类的“删除析构函数”?

考虑这个片段

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 symbolsGNU GCC (g++): Why does it generate multiple dtors?报告_ZN6ParentD0Ev是删除构造函数。

有没有在Parent类上调用这个“删除析构函数”的情况?

子公司的问题:如果没有,是否有办法让gcov/lcov代码覆盖工具(在Detailed guide on using gcov with CMake/CDash?的回答下使用)在其报告中忽略该符号?

+0

答案也是这样:“不,无法获得该功能的覆盖范围?” – RPGillespie 2017-04-20 02:09:11

+0

你有没有想过如何让gcov忽略那个符号? – RPGillespie 2017-04-20 02:14:14

+0

如果我没记错,我只是忽略了使用标准GCOV结构化注释 – rcomblen 2017-04-20 08:37:47

我认为这是因为你有Child对象,而不是Parent对象。

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

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

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

// 1o被破坏,完整的对象的析构函数Child被调用。由于Child继承Parent,因此它会调用基础对象析构函数,这是_ZN6ParentD2EvParent

// 2o是动态分配和删除,并删除析构函数的Child被调用。然后,它会调用Parent基础对象析构函数。在这两个中,都调用基础对象析构函数。

// 3是相同的。它只相当于// 2,除了o的类型。


我测试它在Cygwin &克++ 4.8.3 &视窗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:} 

正如你所看到的,_ZN6ParentD2EvBase基本对象destructur,而Base别人不叫被调用。

然而,_ZN5ChildD0Ev,删除的Child析构函数被调用了两次和_ZN5ChildD1EvChild完整的对象的析构函数,被调用了三次,因为有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

这是否意味着当删除一个对象时,被调用的唯一“删除析构函数”是final/actual类型中的一个,而不是来自inherithance层次结构的任何其他类型?如果是这样,那么显然它永远不会被称为'Parent'。 – rcomblen 2014-09-04 10:11:04

+0

@rcomblen *当然*。 – ikh 2014-09-04 10:12:25

你不能有父对象,所以没有。海湾合作委员会忽略了这种不必要的功能。优化器确实应该删除它,因为它没有被使用,但是我发现GCC也有问题。

正如ikh所解释的,当纯虚拟父类具有虚拟析构函数时,D0析构函数是不必要地生成(并且不可用)。

但是,如果纯虚父类有一个非虚析构函数,你可以删除一个指向父类型,这调用父类的析构函数D0。当然,父类中的非虚拟析构函数很少需要或不打算,所以g ++会发出警告:[-Wdelete-non-virtual-dtor]