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?
Virtual keyword for destructor is necessary when you want different destructors should follow proper order while objects is being deleted through base class pointer.
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.
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.
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.
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.
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.
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,
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.
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:
= 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 = default
ing, 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With