C++ Virtual Destructors in a 4 level inheritance chain.
Asked Answered
T

4

3

I was doing a little experiment with virtual destructors to review - wondering if anyone has a simple explanation for the following (using vs 2010):

I Define class hierarchy A-B-C-D, D inherits C, C inherits B, B inherits A, A is the Base;

ran 2 experiments:

First experiment -

A has a virtual Destructor.

B has a non-Virtual Destructor

C has a virtual Destructor

D has a non virtual Destructor

//----------------------------

Allocate 4 objects on the heap of type D - Point a pointer of A*, B* and C* at the first 3 - Leave the 4th as a D* for Completeness. Delete all 4 Pointers.

As I expected, in all 4 instances, the complete destructor chain is executed in reverse order from D down to A, freeing all memory.

Second Experiment -

A has a non-virtual Destructor ** Changed A to non virtual

B has a non-Virtual Destructor

C has a virtual Destructor

D has a non virtual Distructor

Allocate 4 objects on the heap of type D - Point a pointer of A*, B*, and C* at the first 3 - Leave the 4th as a D* for Completeness.

Deleting C* and D* pointers: the complete destructor chain is executed in reverse order from D down to A, freeing all memory.

Deleting B*: B and then A Destructor is run (leak)

Deleting A*: Only A Destructor is run (leak)

Can anyone explain Why this is?

When D type opjects are allocated in experiment 2, its immediate base class (C) has a virtual destructor - doesnt that tell the compiler to track it with a Vptr and know the memory type? REGARDLESS of the reference?

Thanks Mike

Transitory answered 15/6, 2012 at 16:56 Comment(2)
It would be lovely if you supplied your example code.Cloudcapped
@Cloudcapped I have posted a code snippet that illustrate this case (almost identically) - let me know if there are any comments.Unreel
S
6

When D type opjects are allocated in experiment 2, its immediate base class (C) has a virtual destructor - doesnt that tell the compiler to track it with a Vptr and know the memory type? REGARDLESS of the reference?

No.

In your second test case, A and B don't have vptrs/vtables. (And even if they did, a non-virtual member function would still be resolved statically, not dynamically.)

Put another way, a base class does not "inherit" information (such as whether functions are virtual) from derived classes.

Sojourn answered 15/6, 2012 at 16:58 Comment(5)
If a base class does not inherit information from derived classes, why in experiment 1, does pointing a B reference to a D object (B has a non virtual destructor) still "know" its a D object allocation and run the entire destructor sequence for D type object?Transitory
Because in the first test-case, B's destructor is virtual; if a member function is virtual in the base class, then it's virtual in the derived class, even if you don't explicit declare it so.Sojourn
OK, this makes sense - that's where my misunderstanding was -Transitory
@OliverCharlesworth: I thought that only worked for methods that were overridden, this also applies to destructors? I figured you would have to explicitly declare all of the destructors in the chain virtual. If this is not the case, that is good to know.Bovine
@tjwrona1992: As soon as a method (including dtor) is declared virtual, it's implicitly virtual for all sub-classes too.Sojourn
J
2

When you delete an A* without a virtual destructor, at compile-time the compiler does not know that it will point at runtime to an object with a virtual destructor. The deletion might be of an object with a virtual destructor -- or not. Dynamic binding does not occur.

Judicative answered 15/6, 2012 at 17:1 Comment(3)
OK - So are you saying, the reference (Because it a type A pointer) does not know that at compile time, it will point at an object with a virtual destructor?Transitory
(Incomplete comment above sorry) - yet, in experiment one, at compile, the B object has a virtual destructor in its base class - so when B points to D, the dynamic binding will occur? From this can I conclude the following? 1 - it is the reference (the pointer type) that will determine dynamic binding - not the actual allocated memory, and 2 - as long as somewhere in the inheritence chain there is a virtual destructor at compile time, dynamic binding will occur?Transitory
Happy to help. 1 - yes; 2 - somewhere above the reference type, inclusive, yes.Judicative
C
1

Is your actual question about why one would use virtual vs non-virtual destructors? Cos having a base class with a non-virtual destructor is bad. See the faq

Cloudcapped answered 15/6, 2012 at 17:0 Comment(2)
It reads a lot like a demonstration of the problems that non-virtual destructors bring. All the behaviour he describes seems entirely expected.Cloudcapped
No - but I was missing the note in the faq -Transitory
U
1

I have composed almost identical question, so I thought to share it.

Note that I also added some usage of virtual function within the different Ctor's in order to illustrate how it is work (shortly, in each Ctor, the V-table is updated only "up to it" , meaning the virtual function implementation that will be invoked is the most derived until "this point" of the inheritance chain).

A note of mine: In the sample code that runs the given classes, I also added a creation of "Derived" object (B and D) on the STACK --> in order to emphasis that all the considerations regarding "virtual-ness" of Dtor's are applicable when we use pointers (of whatever type) to the class instance.

class A;

void callBack(A const& a);

class A 
{

    public:
    A() { std::cout << "A Ctor " << std::endl; f1(); callBack(*this); /* f3();*/ }
    ~A() { std::cout << "A Dtor " << std::endl; }

    void f1() { std::cout << "A : f1 " << std::endl; }
    virtual void f2() const { std::cout << "A : f2 " << std::endl; }
    virtual void f3() = 0;
};


class B : public A 
{
    public: 
    B() { std::cout << "B Ctor " << std::endl;  f1(); callBack(*this); f3(); }
    ~B() { std::cout << "B Dtor " << std::endl; }
    void f1 () { std::cout << "B : f1 " << std::endl;}
    void f2() const { std::cout << "B : f2 " << std::endl; }
    virtual void f3() { std::cout << "B : f3 " << std::endl; }

};


class C : public A 
{
    public:
    C() { std::cout << "C Ctor " << std::endl; f1(); callBack(*this); f3(); }
    virtual ~C() { std::cout << "C Dtor " << std::endl; }
    void f1() { std::cout << "C : f1" << std::endl;}
    void f2() const { std::cout << "C : f2" << std::endl; }
    virtual void f3() const { std::cout << "C : f3" << std::endl; }

};

class D : public C 
{
    public:
    D() { std::cout << "D Ctor " << std::endl;  f1(); callBack(*this); }
    ~D() { std::cout << "D Dtor " << std::endl; }
    void f1() { std::cout << "D : f1" << std::endl; }
    void f2() const { std::cout << "D : f2 " << std::endl; }
    virtual void f3() { std::cout << "D : f3 " << std::endl; }

};

void callBack(A const& a) { a.f2(); }

// =================================================================================================================================

int main()
{
    std::cout << "Start of main program" << std::endl;

    std::cout << "Creating a D object on the heap" << std::endl;
    D* pd = new D;
    C* pc = new D;
    A* pa = new D;

    if (true)
    {
        std::cout << "Entering Dummy scope # 1 and creating B object on the stack" << std::endl;
        B b;
        std::cout << "Leaving Dummy scope # 1 with B object within it" << std::endl;
    }

    if (true)
    {
        std::cout << "Entering Dummy scope # 2 and creating D object on the stack" << std::endl;
        D d;
        std::cout << "Leaving Dummy scope # 2 with D object within it" << std::endl;
    }

    std::cout << "Calling delete on pd (D*) which points on a D object" << std::endl;
    delete pd;

    std::cout << "Calling delete on pc (C*) which points on a D object" << std::endl;
    delete pc;

    std::cout << "Calling delete on pa (A*) which points on a D object" << std::endl;
    delete pa;

   std::cout << "End of main program" << std::endl;
   return 0;
}
Unreel answered 23/1, 2017 at 7:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.