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

时间:2014-09-04 09:42:23

标签: c++ c++11 destructor gcov lcov

我在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?回答之后使用)忽略其报告中的该符号?

3 个答案:

答案 0 :(得分: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,因此它会调用<{1}}的基础对象析构函数_ZN6ParentD2Ev

Parent中,动态分配和删除// 2,并调用o删除析构函数。然后,它将调用Child基础对象析构函数。在两者中,都调用了基础对象析构函数。

Parent是一样的。它只等于// 3,除了// 2的类型。


我在cygwin&amp; amp; g ++ 4.8.3&amp; Windows 7 x86 SP1。这是我的测试代码。

o

并编译&amp; gcov选项:

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

这是结果。

$ 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的完整对象析构函数被调用三次,因为{{1} }和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被调用了三次。 (1 + 2)我想这只是GCC的实现 - 减少重复。

答案 1 :(得分:2)

您不能拥有Parent对象,所以没有。 GCC监督是产生这种不必要的功能。优化器真的应该删除它,因为它没有使用,但我发现GCC也存在问题。

答案 2 :(得分:1)

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

但是,如果纯虚拟父类具有非虚拟析构函数,则可以删除指向父类型的指针,此调用父D0析构函数。当然,父类中的非虚拟析构函数很少是可取的或有意的,因此g ++会发出警告:[-Wdelete-non-virtual-dtor]