As much as possible, avoid writing destructors, and prefer the defaultly generated ones. Depending on the style guide your projects follow, that can mean write no destructors.
You write a destructor at most once for each kind of external resource. I say at most, because you can fairly easily write a template for "acquire with Callable1
, release with Callable2
" and not use the syntax for destructors elsewhere.
e.g.
template<typename Acquire, typename Release, typename Token = decltype(std::declval<Acquire>()())>
class RAII {
Release release;
Token token;
public:
RAII(Acquire acquire = {}, Release release = {}) : release(release), token(acquire()) { }
~RAII() { release(token); }
RAII(const RAII &) = delete;
RAII& operator= (const RAII &) = delete;
};
You also often declare destructors for classes designed to be the root of an inheritance hierarchy, because you want them to be virtual, but you can define them inline as = default
. I'm not sure if you also want to count that
e.g.
class Base {
virtual ~Base() = default;
};
And a third case is where your class contains a std::unique_ptr
to an type incomplete at the point of definition, most frequently when you are writing a pImpl. Here you declare a destructor, and then provide a default definition at the point where the pImpl is a complete type
e.g.
// Foo.hpp
class Foo {
class FooImpl;
std::unique_ptr<FooImpl> pImpl;
public:
Foo();
~Foo();
};
// Foo.cpp
class FooImpl {};
Foo::Foo() : pImpl(std::make_unique<FooImpl>()) {}
Foo::~Foo() = default;