I've been studying up on virtual destructors and how they work and I wrote this little program. I've been adding and removing the word "virtual" on lines 9, 16, and 23 to see what happens.

#include <iostream>
using namespace std;


class a
{
public:
    a(){cout << "a::a()" << endl;}
    virtual ~a(){cout << "a::~a()" << endl;}
};

class b: public a
{
public:
    b(){cout << "b::b()" << endl;}
    virtual ~b(){cout << "b::~b()" << endl;}
};

class c: public b
{
public:
    c(){cout << "c::c()" << endl;}
    virtual ~c(){cout << "c::~c()" << endl;}
};

int main()
{
    {
        a* x = new c();
        delete x;
    }
    return 0;
}

Here are my findings...

  • When "virtual" is on all the lines, all destructors are called. Makes sense to me.
  • When "virtual" is on none of the lines, only a's destructor is called. Also makes sense to me.
  • When a's destructor is not virtual and either b's or c's IS virtual, the program crashes. I'm not sure why this one happens.
  • When a's destructor virtual and b's and c's destructors are not virtual, all three destructors are called. This one is really puzzling me. I thought that you started at the base destructor and stopped after the first non-virtual destructor. In this case, b's destructor would not be virtual, so c's should not ahve been called? But it was called.

Finally, is there ever a time when you would not want to define all three destructors as virtual? If b and c had no dynamic memory to delete, it would cause no problems to not call the destructors, but at the same time, what would the harm be?

Any guidance on what that magical word "virtual" does in these cases would be appreciated, particularly what is happening in list items 3 and 4 would be appreciated.

When a's destructor is not virtual and either b's or c's IS virtual, the program crashes. I'm not sure why this one happens.

I don't know off the top of my head, but I'd guesstimate that this invokes undefined behavior somehow and crashing was the result.

When a's destructor virtual and b's and c's destructors are not virtual, all three destructors are called. This one is really puzzling me.

Virtualness is inherited whether you specify it or not. Because a's destructor is virtual, b and c inherited that trait and are also virtual.

Finally, is there ever a time when you would not want to define all three destructors as virtual?

If none of the classes are used polymorphically, there's no need for virtual behavior. But I'd probably question a deep inheritance hierarchy that doesn't use abstract classes as the bases.

Thanks.

is there ever a time when you would not want to define all three destructors as virtual?

Yes and no. Inheritance is mostly used in two contexts: for dynamic polymorphism in object-oriented programming; and for a number of template-heavy techniques in generic programming and template meta-programming. In the former case, it would be incorrect to not make the base-class destructors virtual (which makes all derived-class destructors virtual too!), this is one of the few hard rules about C++ good programming practices.

In the latter case, the classes involved in the inheritance are used to bury some implementation details (and bits of template-kung-fu), or often don't contain any data members and will never be instantiated (as an object), and in either cases, there is no need for virtual destructors because the inheritance hierarchy is not exposed (i.e., the user only sees and uses the top-level derived class, not its base-classes, for example, std::string and STL containers are like that (which explains the ugly, deeply nested error messages you get when you use them wrong, i.e., you catch a glimpse of the underlying class hierarchy)).

If you are using inheritance for other purposes, you are probably doing it wrong.

If b and c had no dynamic memory to delete, it would cause no problems to not call the destructors, but at the same time, what would the harm be?

Be careful with this. Do not confuse the lack of a user-defined destructor, with the lack of a destructor, period. In many cases, especially if you follow good C++ style programming (using only RAII classes), you very very often end up with either empty destructors or no user-defined destructors at all. This is great, but it doesn't mean that the destructors (user-defined as empty, or compiler-generated) do not need to be called (to automatically destroy data members). But, sure, there are cases where the destructor call is not necessary, but these cases rarely (if ever) apply to object-oriented polymorphic classes (where you would use virtual destructors), and when it does apply, you can trust that the compiler will optimize away the destructor call (e.g., if the class is a POD-type).

you very very often end up with either empty destructors or no user-defined destructors at all. This is great, but it doesn't mean that the destructors (user-defined as empty, or compiler-generated) do not need to be called (to automatically destroy data members).

I'm confused here. If I write a destructor with no code in the body, then it gets called when the object is destroyed and an empty function is executed. If the destructor has no body, why does it matter whether it's called or whether it is written or even exists? I got in the habit of writing empty destructors mainly because my instructors insisted upon it and took off points if you didn't, but truth be told, I never thought about why.

In other words, if no dynamic memory was allocated anywhere, why do we even care about destructors? Presumably my instructors were taking points off when you left them off for reasons other than simply being mean.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.