Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I default virtual destructors?

I have an abstract class that is declared as follow:

class my_type {
public:
    virtual ~my_type() = default;
    virtual void do_something() = 0;
};

Is it considered good practice to declare the destructor like this, with the default keyword? Is there a better way?

Also, is = 0 a modern (C++11) way of specifying no default implementation, or there is a better way?

like image 200
Humam Helfawi Avatar asked Dec 20 '15 17:12

Humam Helfawi


People also ask

Should I always make destructor virtual?

Virtual keyword for destructor is necessary when you want different destructors should follow proper order while objects is being deleted through base class pointer.

Are destructors virtual by default?

The destructor is not virtual (that is, the base class destructor is not virtual) All direct base classes have trivial destructors. All non-static data members of class type (or array of class type) have trivial destructors.

Should you override virtual destructor?

Whenever you are dealing with inheritance, you should make any explicit destructors virtual. As with normal virtual member functions, if a base class function is virtual, all derived overrides will be considered virtual regardless of whether they are specified as such.

Should virtual destructors be protected?

So if you don't want callers to do delete bar / delete foo and want both base and derived classes' destructors to be invoked, both destructors must be protected and at least the Base class' destructor must be virtual.

When should I not use the virtual destructor?

This is a good example of when to not use the virtual destructor: From Scott Meyers: If a class does not contain any virtual functions, that is often an indication that it is not meant to be used as a base class. When a class is not intended to be used as a base class, making the destructor virtual is usually a bad idea.

Which classes should have a virtual destructor?

Any class that is inherited publicly, polymorphic or not, should have a virtual destructor. To put another way, if it can be pointed to by a base class pointer, its base class should have a virtual destructor. If virtual, the derived class destructor gets called and then the base class destructor.

Why base class destructors are virtual in C++?

Making base class destructor virtual guarantees that the object of derived class is destructed properly, i.e., both base class and derived class destructors are called. For example,

Should a polymorphic class have a virtual destructor?

Any class that is inherited publicly, polymorphic or not, should have a virtual destructor. To put another way, if it can be pointed to by a base class pointer, its base class should have a virtual destructor. If virtual, the derived class destructor gets called, then the base class constructor.


1 Answers

Yes, you can definitely use = default for such destructors. Especially if you were just going to replace it with {}. I think the = default one is nicer, because it's more explicit, so it immediately catches the eye and leaves no room for doubt.

However, here are a couple of notes to take into consideration when doing so.

When you = default a destructor in the header file (see edit) (or any other special function for that matter), it's basically defining it in the header. When designing a shared library, you might want to explicitly have the destructor provided only by the library rather than in the header, so that you could change it more easily in the future without requiring a rebuild of the dependent binary. But again, that's for when the question isn't simply whether to = default or to {}.


EDIT: As Sean keenly noted in the comments, you can also use = default outside of the class declaration, which gets the best of both worlds here.


The other crucial technical difference, is that the standard says that an explicitly defaulted function that can't be generated, will simply not be generated. Consider the following example:

struct A { ~A() = delete; };
struct B : A { ~B() {}; }

This would not compile, since you're forcing the compiler to generate the specified code (and its implicit requisites, such as calling A's destructor) for B's destructor--and it can't, because A's destructor is deleted. Consider this, however:

struct A { ~A() = delete; };
struct B : A { ~B() = default; }

This, in fact, would compile, because the compiler sees that ~B() cannot be generated, so it simply doesn't generate it--and declares it as deleted. This means that you'll only get the error when you're trying to actually use/call B::~B().

This has at least two implications which you should be aware of:

  1. If you're looking to get the error simply when compiling anything that includes the class declaration, you won't get it, since it's technically valid.
  2. If you initially always use = default for such destructors, then you won't have to worry about your super class's destructor being deleted, if it turns out it's ok and you never really use it. It's kind of an exotic use, but to that extent it's more correct and future-proof. You'll only get the error if you really use the destructor--otherwise, you'll be left alone.

So if you're going for a defensive programming approach, you might want to consider simply using {}. Otherwise, you're probably better off = defaulting, since that better adheres to taking the minimum programmatic instructions necessary to get a correct, working codebase, and avoiding unintended consequences1.


As for = 0: Yes, that's still the right way to do it. But please note that it in fact does not specify that there's "no default implementation," but rather, that (1) The class is not instantiatable; and (2) Any derived classes must override that function (though they can use an optional implementation provided by the super class). In other words, you can both define a function, and declare it as pure virtual. Here's an example:

struct A { virtual ~A() = 0; }
A::~A() = default;

This will ensure these constraints on A (and its destructor).


1) A good example of why this can be useful in unexpected ways, is how some people always used return with parentheses, and then C++14 added decltype(auto) which essentially created a technical difference between using it with and without parentheses.

like image 101
Yam Marcovic Avatar answered Oct 20 '22 03:10

Yam Marcovic