Search code examples
c++c++11destructorgcovlcov

Is there a way to call the "deleting destructor" of a pure virtual class?


I'm using C++11 and g++4.8 on Ubuntu Trusty.

Consider this snippet

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

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

Called using

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

I use gcov to generate my code coverage report. It report that the destructor with symbol _ZN6ParentD0Ev is never called, while _ZN6ParentD2Ev is.

Answer Dual emission of constructor symbols and GNU GCC (g++): Why does it generate multiple dtors? reports that _ZN6ParentD0Ev is the deleting constructor.

Is there any case where this "deleting destructor" is called on the Parent class ?

Subsidiary question: if not, is there a way to get the gcov/lcov code coverage tool (used following answer of Detailed guide on using gcov with CMake/CDash?) ignore that symbol in its report ?


Solution

  • I think it's because you have the Child object, not Parent object.

    {
        Child o;
        o.f();
    } // 1
    
    {
        Parent * o  = new Child;
        delete o;
    } // 2
    
    {
        Child * o  = new Child;
        delete o;
    } // 3
    

    In // 1, o is destroyed, and the complete object destructor of Child is called. Since Child inherits Parent, it'll call the base object destructor, which is _ZN6ParentD2Ev, of Parent.

    In // 2, o is dynamically allocated and deleted, and the deleting destructor of Child is called. Then, it'll call the base object destructor of Parent. In both, base object destructor is called.

    // 3 is same. it's just equal to // 2, except o's type.


    I've tested it on cygwin & g++ 4.8.3 & windows 7 x86 SP1. Here's my test code.

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

    and compile & gcov option:

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

    Here's the result.

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

    As you can see, _ZN6ParentD2Ev, the base object destructur of Base, is called while the others of Base are not called.

    However, _ZN5ChildD0Ev, deleting destructor of Child, is called twice and _ZN5ChildD1Ev, complete object destructor of Child, is called three times, since there's delete o; and Child o;.

    But according my explanation, _ZN5ChildD0Ev should be called twice and _ZN5ChildD1Ev should be called once, shouldn't it? To figure out the reason, I did this:

    $ objdump -d test > test.dmp
    

    Result:

    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
    

    Yeah, since _ZN5ChildD0Ev calls _ZN5ChildD1Ev, _ZN5ChildD1Ev was called three times. (1 + 2) I guess it's just GCC's implementation - for reduce duplication.