Deleting dynamically allocated objects with private destructors

Viewed 296

So I came across a code snippet which demonstrated that if we want forced dynamic allocation of any class object, we should make its destructor private.

I tried that and yes it doesn't allow one to instantiate object on stack. But when I instantiated a dynamically allocated instance and tried deleting the object (or it would cause leak) - I kept on getting warning about destructor being private.

How can I properly manage memory of a dynamically allocated object which has a private destructor?

4 Answers

Like accessing any other private member function, you must do it in a member or a friend function. An example:

class foo {
    ~foo(){}
public:
    static void del(const foo* ptr)
    {
        delete ptr;
    }
};

Or even better, force the client to use smart pointer:

class foo {
    ~foo(){}
    struct deleter {
        void operator()(const foo* ptr)
        {
            delete ptr;
        }
    };
public:
    static std::unique_ptr<foo, deleter> make(/*args*/)
    {
        return {new foo(/*args*/), {}};
    }
};

Providing a deleting function works as far as access control goes, but forces the users of your class to use custom deleters everywhere. It is more concise all around to just befriend the standard default deleter:

struct Foo {
private:
    friend std::default_delete<Foo>;
    ~Foo() = default;
};

auto p = std::make_unique<Foo>(); // Works out of the box

The only good reason to force an object to be dynamically allocated is that it needs to manage its own lifetime somehow. Otherwise, the code that creates the object is responsible for managing its lifetime---and as automatic storage duration is a valid lifetime management strategy, it ought not to be disabled intentionally.

So, I will suppose that your object manages its own lifetime; for example, perhaps it maintains a reference count, then calls delete this in the release() method when the reference count goes to 0. Then the answer to the question of "how to properly manage the object's lifetime", as a user of the object, is "use the object properly", so that the object will deallocate itself when the time is right.

For example, a std::unique_ptr with a custom deleter can be used to ensure that the object's release() is called on scope exit, preventing any references from being leaked.

Here's one example:

class Example {
public:
    void kill_me() { // a public function
        delete this; // is allowed to delete
    }
private:
    ~Example() {}    // private destructor
};

int main() {
    Example* e = new Example;
    e->kill_me();
}
Related