7

Although using =default for constructors is clear for me (i.e. forcing the compiler to create the default constructor while other ctors exist), I still cannot understand the difference between these two type of destructors:

  1. Those that use =default
  2. Those that are not defined explicitly and generated by compiler automatically.

The only thing that comes to my mind is that the group-1 destructors can be defined as virtual, but group-2 is always non-virtual. So, is that the only difference between them? Is there any scenarios that the compiler is not generating the destructor, but using =default forces the compiler to generate it?

p.s. I have checked lots of Qs in stackoverflow, but none of them answers my Q. Here are some relevant questions.

  1. Difference between =default and {} ctos/destructors
  2. Defaulting virtual destructors
  3. Difference between =default and empty dtrs

Edit 1: This Q on SO, focuses on disabling the default move constructors, that can be considered as ONE of the items mentioned in the accepted answer.

TonySalimi
  • 8,257
  • 4
  • 33
  • 62
  • Defaulting a virtual or pure-virtual destructor are the only two applicable cases I'm aware of. – David G Jun 27 '19 at 22:36
  • `=default` is considered a user-defined destructor. I'm wracking my brain trying to remember any behaviours that change if a class has a user-defined destructor vs no specified destructor. This sort of thing happens all the time with constructors, but I can't think of any for destructors. – user4581301 Jun 27 '19 at 22:43
  • 4
    The destructor itself behaves the same for `= default` versus omitting it; but the presence of the declaration suppresses implicit generation of the move-constructor and move-assignment – M.M Jun 27 '19 at 22:48
  • 2
    Those generated by the compiler implicitly will need complete types, which is not possible if you're making a `pimpl` class. You can then declare `~X();` and do `~X() = default;` in the implementation, after the `pimpl` type is complete. – Ted Lyngmo Jun 27 '19 at 22:57
  • @TedLyngmo: But how is `=default` distinct from `{}` in that case? – Davis Herring Jun 28 '19 at 00:35
  • 1
    Other cases: you wish to make it `protected` or `private`. You wish to put it in a .cpp file so that it isn't inlined. – Nevin Jun 28 '19 at 00:59
  • *'but the presence of the declaration suppresses implicit generation of the move-constructor and move-assignment'* – but if that should be the reason for writing it at all, then explicitly `delete`ing the move constructor and assignment is better choice... – Aconcagua Jun 28 '19 at 06:20
  • I still don't see the difference between `destructor() { }` and `destructor() = default;` – I would suppose that defaulting the constructor is mainly for consistency reasons: One might have defaulted constructors and assignments already (possibly not all of them...). Then it simply looks better if the destructor follows the same pattern as the others... – Aconcagua Jun 28 '19 at 06:25
  • @DavisHerring Afaik, there is no difference in the case I presented. I should have written: "_Those generated by the compiler implicitly will need complete types, which is not possible if you're making a `pimpl` class. You can then declare `~X();` and define `~X() = default;` or `~X() {}` in the implementation, after the `pimpl` type is complete._" to point at one possible diff between implicit and explicit. – Ted Lyngmo Jun 28 '19 at 08:38
  • @TedLyngmo Can you show that with a simple code snippet somewhere? – TonySalimi Jun 28 '19 at 09:06
  • 1
    @Aconcagua I think the main difference is being trivial and non-trivial while using =default or {}, as mentioned in this answer: https://stackoverflow.com/a/13578720/896012 – TonySalimi Jun 28 '19 at 09:13
  • @Hoodi Take a look at the [widget](https://en.cppreference.com/w/cpp/language/pimpl#Example) example. – Ted Lyngmo Jun 28 '19 at 09:19
  • @Aconcagua: A deleted move constructor or assignment operator participates in overload resolution (unless it is implicitly declared), which is not the same as not having one at all. – Davis Herring Jun 28 '19 at 13:40
  • @DavisHerring Hm, overlooked the situation where we wouldn't want to delete *both*... But for the case we'd just wanted one of them providing the other one with `= default` again appears the better choice to me... – Aconcagua Jun 29 '19 at 10:02
  • @DavisHerring I think this Q is more general that you have mentioned. The referenced Q, just focuses on disability of move move constructors, but as you have answered, there are other differences as well. – TonySalimi Mar 20 '20 at 10:42
  • @Gupta: You’re right—I pasted the wrong link! I can’t vote again (soon), but it should have been [this more general question](https://stackoverflow.com/q/31672207/8586227) instead. (I do still like my own answer.) – Davis Herring Mar 20 '20 at 14:00

1 Answers1

8

(Several of the points below were already mentioned in comments or in the linked questions; this answer serves to organize and interrelate them.)

There are of course three ways to get a “simple destructor”:

struct Implicit {};
struct Empty {~Empty() {}};
struct Defaulted {~Defaulted()=default;};

Like a default (and not a copy or move) constructor, {} and =default; mean largely the same thing for destructors. The interesting properties of Defaulted are then those (combinations) that differ from both of the others.

Versus Empty the main difference is simple: the explicitly defaulted destructor can be trivial. This applies only if it is defaulted inside the class, so there is no difference between {} and =default; on an out-of-line definition. Similarly, being virtual removes any distinction, as does having any member or base class with a non-trivial destructor. There is also the distinction that an explicitly defaulted destructor can be implicitly defined as deleted. Both of these properties are shared with implicitly declared destructors, so we have to find a distinction from those as well.

Versus Implicit, an explicitly defaulted destructor suppresses move operations, can be declared private, protected, or noexcept(false), and in C++20 can be constrained (but not consteval). Very marginally, it can be declared constexpr to verify that it would be anyway. Declaring it inline doesn’t do anything. (It can also be out of line or virtual, but as stated above that can’t be a reason to use it.)

So the answer is “when you want a trivial (or potentially deleted) destructor that has other special properties”—most usefully, access control or noexcept status.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76