0

For what situations we need to have a default generated destructor? It's pretty clear why we would need default generated constructors and operator=, but can't think of situation when default generated destructor should be used.

class A
{
...
~A() = default;
...
};
DoehJohn
  • 201
  • 1
  • 8
  • 1
    I'm pretty sure `~A() = default;` is always equivalent to `~A() {}`, so the syntax is provided mostly for symmetry; it would be odd if `=default` were allowed in some places but not others. As to why you may need to explicitly write `~A() {}` rather than omit the destructor entirely - e.g. when you want to make it `virtual`. – Igor Tandetnik Jul 18 '20 at 22:47
  • 2
    @IgorTandetnik, https://gcc.godbolt.org/z/eh3Waz – chris Jul 18 '20 at 22:52
  • 3
    An empty destructor turns a trivial type into a non-trivial type. It could cause some template code to take a non-optimal path. – PaulMcKenzie Jul 18 '20 at 22:57
  • @DoehJohn : https://stackoverflow.com/questions/4943958/conditions-for-automatic-generation-of-default-copy-move-ctor-and-copy-move-assi – Robert Andrzejuk Jul 18 '20 at 22:59
  • I can think of one case: You want it to be explicitly defaulted, but need to delay it, as when doing a `pimpl` thingy. and use a `unique_ptr` - so you declare`~A();` - then later define: `A::~A() = default;` – Ted Lyngmo Jul 18 '20 at 23:09
  • @RobertAndrzejuk ye, in the answers on that link it's stated that default dtor will always be generated – DoehJohn Jul 19 '20 at 11:21
  • @TedLyngmo default dtor will be generated if I didn't write my own, can't see in your example how A::~A() = default; will change something.. – DoehJohn Jul 19 '20 at 11:23
  • 1
    @DoehJohn I made an example out of it. – Ted Lyngmo Jul 19 '20 at 13:01

3 Answers3

2

In cases where you'd like to hide the implementation of a class inside an inner class and keep a unique_ptr to an instance of that inner class (the pimpl idiom) you need to move the default destructor definition out of the class definition since unique_ptr can't work with incomplete types.

Example:

A.hpp (the header a user of the class will include)

#pragma once
#include <memory>

class A {
public:
    A();
    ~A();
    void foo() const;
private:
    struct A_impl; // just forward declared
    std::unique_ptr<A_impl> pimpl;
};

A_impl.hpp ("hidden" - not to be included in normal usage of A)

#pragma once
#include "A.hpp"

struct A::A_impl {
    void foo() const;
};

A.cpp

#include "A_impl.hpp"

A::A() : pimpl(std::make_unique<A_impl>()) {}
A::~A() = default;                            // <- moved to after A_impl is fully defined
void A::foo() const { pimpl->foo(); }

A_impl.cpp

#include "A_impl.hpp"

#include <iostream>

void A::A_impl::foo() const { std::cout << "foo\n"; }

Demo

If you let the compiler generate A::~A() it will not compile. My compiler says:

unique_ptr.h:79:16: error: invalid application of ‘sizeof’ to incomplete type ‘A::A_impl’
     static_assert(sizeof(_Tp)>0,
                   ^~~~~~~~~~~

Demo

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • If you declare your class as class A { public: A(); void foo() const; private: struct A_impl; // just forward declared std::unique_ptr pimpl; }; you have this error? – DoehJohn Jul 20 '20 at 15:21
  • 1
    @DoehJohn Yes, the dtor must be defined after `A_impl` is fully defined, so this is a case where `~A() = default;` makes perfect sense. I added two demos so you can see it live. – Ted Lyngmo Jul 20 '20 at 18:52
  • Thanks a lot for your time! It's a bit confusing that adding destructor to A solves the error about incomplete type of ‘A::A_impl’ – DoehJohn Jul 21 '20 at 00:20
  • 1
    @DoehJohn You're welcome. Yeah, I guess it can be a bit confusing at first. [This](https://www.fluentcpp.com/2017/09/22/make-pimpl-using-unique_ptr/) looks like a pretty good walk-through. – Ted Lyngmo Jul 21 '20 at 03:41
0

C++ Core Guidelines C.21: If you define or =delete any copy, move, or destructor function, define or =delete them all

Reason

The semantics of copy, move, and destruction are closely related, so if one needs to be declared, the odds are that others need consideration too.

Declaring any copy/move/destructor function, even as =default or =delete, will suppress the implicit declaration of a move constructor and move assignment operator. Declaring a move constructor or move assignment operator, even as =default or =delete, will cause an implicitly generated copy constructor or implicitly generated copy assignment operator to be defined as deleted. So as soon as any of these are declared, the others should all be declared to avoid unwanted effects like turning all potential moves into more expensive copies, or making a class move-only.

Note If you want a default implementation (while defining another), write =default to show you're doing so intentionally for that function. If you don't want a generated default function, suppress it with =delete.

So this mainly depends on what is declared in the class.

Generally it is about The rule of three/five/zero

If the class needs a custom copy/move function, but nothing special for a destructor, then =default should be used on the destructor.

Robert Andrzejuk
  • 5,076
  • 2
  • 22
  • 31
  • but default dtor always will be generated if programmer doesn't wrote destructor on it's own – DoehJohn Jul 19 '20 at 11:22
  • @DoehJohn You are correct - it will be created. The Core Guidelines have the following "Note: If you want a default implementation (while defining another), write `=default` to show you’re doing so intentionally for that function." – Robert Andrzejuk Sep 17 '20 at 12:39
0

This seems to be asking when you would define the destructor for a class if the body of that destructor would be the same as the one the compiler generates.

Reasons include:

  1. Clarity. If you have a class with copy/move constructors or copy/move assignment operators, it is typically managing some resource. Many coding guidelines would require you define the destructor to show that it wasn't just overlooked, even it is equivalent to the compiler-generated one.
  2. Some aspect of the function differs from the one the compiler would generate. If you want a virtual destructor, you have to define it. Similarly, a throwing destructor must be defined.
  3. You want to control the place the destructor is generated. You can define a destructor outside of the class definition. You might need to do this for cyclically dependent classes as in one of the other answers. You may want to do this to define a stable ABI. You may want to do this to control code generation.

In all these cases, you must or want to define the destructor, even though the body is nothing special. Why would you use = default versus an empty body? Because the compiler-generated destructor is equivalent to the one you get with = default, and you only want to change the aspects of the destructor you are trying to change. An empty body is not the same as = default in C++, because a defaulted function can be defined as deleted. An empty body also rules out trivial destructibility, even if that was otherwise an option.

Jeff Garrett
  • 5,863
  • 1
  • 13
  • 12