有没有办法调用纯虚拟类的“删除析构函数”?
Posted
技术标签:
【中文标题】有没有办法调用纯虚拟类的“删除析构函数”?【英文标题】:Is there a way to call the "deleting destructor" of a pure virtual class? 【发布时间】:2014-10-29 00:11:35 【问题描述】:我在 Ubuntu Trusty 上使用 C++11 和 g++4.8。
考虑一下这个sn-p
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
被调用。
Answer 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? 的答案之后使用)在其报告中忽略该符号?
【问题讨论】:
所以答案是“不,没有办法覆盖该功能?” 你有没有想过如何让 gcov 忽略该符号? 如果我没记错的话,我只是忽略了使用标准 GCOV 结构化 cmets 的析构函数的覆盖范围 您是在谈论 LCOV 排除标记吗? ltp.sourceforge.net/coverage/lcov/geninfo.1.php 好的,是的,我能够在派生类周围使用LCOV_EXCL_START
和LCOV_EXCL_STOP
来抑制它。
【参考方案1】:
我认为这是因为您拥有 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
,它会调用Parent
的基础对象析构函数,即_ZN6ParentD2Ev
。
在// 2
中,动态分配和删除o
,调用Child
的deleting析构函数。然后,它会调用Parent
的基础对象析构函数。两者都调用了基础对象析构函数。
// 3
是一样的。它只是等于// 2
,除了o
的类型。
我已经在 cygwin & g++ 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
,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
被调用了 3 次。 (1 + 2) 我猜这只是 GCC 的实现 - 用于减少重复。
【讨论】:
这是否意味着在删除对象时,唯一调用的“删除析构函数”是最终/实际类型之一,而不是继承层次结构中的任何其他类型?如果是这样,那么显然它永远不会在Parent
上被调用。【参考方案2】:
你不能有 Parent 对象,所以没有。生成这个不必要的函数是 GCC 的疏忽。优化器确实应该删除它,因为它没有被使用,但我发现 GCC 也有问题。
【讨论】:
【参考方案3】:正如 ikh 所解释的,当纯虚拟父类具有虚拟析构函数时,D0 析构函数是不必要的生成(并且不可用)。
然而,如果纯虚拟父类有一个非虚拟析构函数,你可以删除一个指向父类型的指针,这将调用父类的D0析构函数。当然,父类中的非虚拟析构函数很少是可取的或有意的,因此 g++ 发出警告:[-Wdelete-non-virtual-dtor]
。
【讨论】:
以上是关于有没有办法调用纯虚拟类的“删除析构函数”?的主要内容,如果未能解决你的问题,请参考以下文章