Should I default virtual destructors?
Asked Answered
R

1

80

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?

Rolph answered 20/12, 2015 at 17:5 Comment(14)
What's wrong with virtual ~foo(){}?Undesigned
nothing I just thought that the C++11 way is to use default keyword.. is not it ?Rolph
@Humam Note that you misplaced virtual and the data return type.Jabber
{} is shorter, familiar, and works everywhere. Looks like a no-brainer.Undesigned
virtual void foo().= 0 defines foo as an abstract/pure virtual function - the class wont implement it, but its inheritors will. more info hereInsincere
@n.m. = default and {} have subtly different behaviour so it's not quite a no-brainer.Acerbic
@M.M. even if they are virtial?Undesigned
@Insincere You've got a typo there, the syntax foo().= 0 with the . does not exist, unless it's something really crazy that I've never heard about.Pharsalus
@Pharsalus do you have a dirty screen? I see no period there ;)Insincere
@Insincere I can confirm too.. there is a dot!Rolph
@HumamHelfawi How odd... Perhaps an issue with character encoding? I see nothing on any of my devicesInsincere
OH! You guys mean in my comment, and I'm looking in my question. SE's comment editing window keeps me from fixing that typo, which is likely why it's there at all! Sorry for the confusion.Insincere
@Insincere I suspected that it was an encoding problem and it thrilled me to know what the problem is. After all, it was misunderstanding problem :D have a nice day!Rolph
@Insincere Haha! I wasn't sure first whether you're making a joke or not :-) I see, probably SO doesn't allow editing a comment from a couple years ago!Pharsalus
S
77

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.

Shaving answered 20/12, 2015 at 17:15 Comment(5)
I usually default whole "big 5", per Rule of 5/0. And just for information: if you for some reason want to make destructor pure virtual, you still need to provide an implementation for it.Theta
"One case where I wouldn't = default a destructor (or any other special function for that matter), is when I want to make sure its definition doesn't go in the header file" -- you can put =default on function definitions outside of the initial class definition, too. I do that all the time specifically in cases where I don't care about trivial destructability and just want to reduce linker pressure.Fritzsche
In your example "Consider this, however:", B's destructor would be defined as deleted, which is different from "doesn't generate it." .Acerbic
If I declare (defaulted) the destructor is it good practice to declare (defaulted) the move ctor/assignment too? What about copy ctor/assignment?Fabio
@Fabio User-defined default destructor deletes the implicit move constructor, so you should create it explicitly if you want to move the class. Copy constructor is not affected.Bahena

© 2022 - 2024 — McMap. All rights reserved.