64
votes

Are there any circumstances in which it is legitimate for a derived class to have a non-virtual destructor? A non-virtual destructor signifies that a class should not be used as a base-class. Will having a non-virtual destructor of a derived class act like a weak form of the Java final modifier?

I am especially interested in the case where the base class of the derived class has a virtual destructor.

11

11 Answers

91
votes

Are there any circumstances in which it is legitimate for a derived class to have a non-virtual destructor?

Yes.

A non-virtual destructor signifies that a class should not be used as a base-class.

Not really; a non-virtual destructor signifies that deleting an instance of derived via a base pointer will not work. For example:

class Base {};
class Derived : public Base {};

Base* b = new Derived;
delete b; // Does not call Derived's destructor!

If you don't do delete in the above manner, then it will be fine. But if that's the case, then you would probably be using composition and not inheritance.

Will having a non-virtual destructor of a derived class act like a weak form of the Java final modifier?

No, because virtual-ness propagates to derived classes.

class Base
{
public:
    virtual ~Base() {}
    virtual void Foo() {};
};

class Derived : public Base
{
public:
    ~Derived() {}  // Will also be virtual
    void Foo() {}; // Will also be virtual
};

There isn't a built-in language mechanism in C++03 or earlier to prevent subclasses(*). Which isn't much of an issue anyway since you should always prefer composition over inheritance. That is, use inheritance when a "is-a" relationship makes more sense than a true "has-a" relationship.

(*) 'final' modifier was introduced in C++11

33
votes

It is perfectly valid to have an Base class with an non virtual destructor if you are never going to call delete on a Base class pointer pointing to an derived class object.

Follow Herb Sutter's Advice:

Guideline #: Only if derived classes need to invoke the base implementation of a virtual function, make the virtual function protected. For the special case of the destructor only:

Guideline #: A base class destructor should be either public and virtual, or protected and nonvirtual.


Maybe your question actually is:
Does Destructor in Derived class needs to be virtual if Base class Destructor is virtual?

The answer is NO.
If Base class destructor is virtual then the Derived class destructor is implicitly virtual already, you don't need to specify it as virtual explicitly.

14
votes

Addresssing the latest edit:

Edit: I am especially interested in the case where the base class of the derived class has a virtual destructor.

In that case, the destructor of the derived class will be virtual, regardless of whether you add the virtual keyword or not:

struct base {
   virtual ~base() {}       // destructor is virtual
};
struct derived : base {
   ~derived() {}            // destructor is also virtual, because it is virtual in base
};

This is not limited to destructors, if at any point in a type hierarchy a function member is declared virtual, all overrides (not overloads) of that same function will be virtual, whether they are declared as so or not. The specific bit for destructors is that ~derived() overrides virtual ~base() even if the name of the member differs --that is the only specificity for destructors here.

5
votes

You're question isn't really clear. If the base class has a virtual destructor, the derived class will have one, regardless. There's no way to turn virtuality off, once it's been declared.

And there are certainly cases where it makes sense to derive from a class which doesn't have a virtual destructor. The reason why the base class destructor should be virtual is so that you can delete through a pointer to the base class. If the derivation is private, you don't have to worry about this, since your Derived* won't convert to a Base*. Otherwise, I've seen the recommendation that if the base class destructor isn't virtual, it should be protected; this prevents the one case of undefined behavior (deleting through a pointer to base) that could occur. In practice, however, a lot of base classes (e.g. std::iterator<>) have semantics such that it doesn't even occur to anyone to create pointers to them; much less delete through such pointers. So adding the protection may be more effort than it's worth.

3
votes

Depends on the purpose of your class. Sometimes it is a good practice to make your destructor protected, but not virtual - that basically says: "You shall not delete an object of derived class via a base-type pointer"

2
votes

If your derived class doesn't add any data members to the base class, and has an empty destructor body, then it won't matter if the destructor is virtual or not - all the derived destructor will do is call the base one anyway. It isn't recommended because it's far too easy for someone to come along and modify the class without being aware of these restrictions.

If you never try to delete an object through a pointer to the base class, you'll be safe. This is another rule that's hard to enforce and should be used with care.

Sometimes you don't have any control over the base class and you're forced to derive from it, even though the destructor isn't virtual.

Finally, having a non-virtual destructor in the base class doesn't impose any restriction on the derived class that will be enforced by the compiler, so I don't think it resembles Java's final at all.

1
votes

Yes, there are:

void dothis(Base const&);

void foo() {
  Derived d;
  tothis(d);
}

Here the class is used polymorphically, yet delete is not called, thus it's fine.

Another example would be:

std::shared_ptr<Base> create() { return std::shared_ptr<Base>(new Derived); }

because a shared_ptr is able to use a non-polymorphic delete (through type erasure).

I implemented a warning in Clang specifically to detect the call of delete on polymorphic non-final classes with non-virtual destructors, so if you use clang -Wdelete-non-virtual-dtor, it will warn specifically for this case.

1
votes

A non-virtual destructor is perfectly fine as long as you you don't want to use it as a base pointer for derived classes when deleting the object.

If you its derived classes in a polymorphic way, passing and storing it with a base pointer and then deleting it then the answer is no, use a virtual destructor.

0
votes

Yes, No and No.

Virtual destructor has nothing to do with ability of the class to be a base or a derived class. It is a legitimate one as both.

However, there are certain reasons to make destructors virtual. See here: http://en.wikipedia.org/wiki/Virtual_destructor#Virtual_destructors . This makes a class, among other things, have a virtual table if it doesn't have one already. However, virtual tables are not required by C++ to do inheritance.

0
votes

Will having a non-virtual destructor of a derived class act like a weak form of the Java final modifier?

Not at all. Here is my suggestion to prevent sub classes in C++ (like final modifier in Java); make the destructor private in a class. Then you can prevent making sub-classes from it

0
votes

You may not want to create virtual destructor in base class? No destructor do in this case. If you use pointer to base class and create non-virtual destructor in parent one compilator automatically generate this warning! You may block it if you want to create final parent class. But best is to mark it as a final like:

class Base{
public:
    //No virtual destructor defined
    virtual void Foo() {};
};

class Derived final : public Base{
public:
    ~Derived() {}  // define some non-virtual destructor
    void Foo() {}; // Will also be virtual
};

In this case compilator knows what you want and no warnings generate. The decision with empty virtual base destructor is not too well but acceptable. You needn't set attribute final. But this isn't the thing you want.You also needn't define empty virtual base method Foo too Best is:

class Base{
public:
  //No virtual destructor defined
  virtual void Foo() = 0; // abstract method
};
class Derived final : public Base{
public:
  ~Derived() {}  // define some non-virtual destructor
  void Foo() {}; // Will also be virtual
};

It's full clear code for compilator. No warnings generate and no spare code used too.