5

In the standard c++ library, all containers and all input/output streams have their own constructors and destructors, that handle all the relevant resource acquisition and release. So for most tasks that would require destructors (e.g. memory and file management), the modern developer does not have to define the destructors him/herself.

One case when a destructor should be defined explicitly is handling database connections. But this is quite rare - it can be handled by at most a single class in an application.

My question is: how often does a C++ programmer today needs to actually write a destructor for his/her own classes? And, what are the main use-cases for defining a destructor, that are not already handled by standard libraries?

  • I imagine everyone has experience pertaining to his or her own development in C++, but that answer could vary from person to person. Though there's nothing "wrong" with destructors per se. Bottom line: Use them if you need them. – Neil Feb 15 at 8:59
  • Spontaneously I'd say that every class that follows RAII needs a destructor. These classes should be rather small and have a single purpose: unique_ptr, shared_ptr, mutex, file, database connection, ... All other classes should use these small RAII wrappers and then dont need their own destructor (or just a defaulted one). – pschill Feb 15 at 9:00
  • 2
    @pschill Every class that provides a RAII abstraction needs a destructor. Any class that just uses RAII abstractions should be fine with the default constructor. That's the whole point of RAII... – cmaster Feb 15 at 18:34
  • 1
    @cmaster Yes that's what I meant. My words "follows RAII" were poorly chosen, "provides RAII" seems much clearer. – pschill Feb 16 at 9:48
6

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;
  • 1
    Not “avoid writing destructors”, but “avoid creating situations where you need a destructor” (or you can bet someone will end up doing some cleanup call in any situation where an object is going to be deleted). – gnasher729 Feb 16 at 17:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.