Is destroying form of operator delete required to actually destroy the object?

Viewed 199

C++20 has added destroying form of operator delete distinguished by the std::destroying_delete_t parameter. It causes delete expression to no longer destroy the object prior to invoking operator delete.

The intention is to allow customization of deletion in a way that depends on the object's state, before explicitly invoking the object's destructor and deallocating memory.

However, it isn't clear to me if, when implementing such an operator, I'm actually required to destroy the object. Specifically, am I allowed to have a pool of static objects, and give them out to users who can subsequently treat them as-if they were dynamically allocated? Such that delete expression executed on the object will merely return it to the pool without destroying it. For example, is the following program well-defined?

#include <new>
 
struct A {
    virtual ~A() = default;
};

// 'Regular' dynamically allocated objects
struct B : A {
    static A* create() {
        return new B();
    }

private:
    B() = default;
};

// Pooled, statically allocated objects
struct C : A {
    static A* create() {
        for (auto& c: pool) {
            if (!c.in_use) {
                c.in_use = true;
                return &c;
            }
        }
        throw std::bad_alloc();
    }

private:
    static C pool[3];

    bool in_use = false;

    C() = default;

    void operator delete(C *c, std::destroying_delete_t) {
        c->in_use = false;
    }
};

C C::pool[3];

// Delete them identically via the common interface.
void do_something_and_delete(A* a) {
    delete a;
}

int main() {
    do_something_and_delete(B::create());
    do_something_and_delete(B::create());
    do_something_and_delete(C::create());
    do_something_and_delete(C::create());
}
1 Answers

The purpose of destroying delete operators, as defined by its proposal, is to effectively deal with the ability to create and destroy objects whose deallocation and destruction needs access to the object, for one reason or another. It does this by preventing the automatic invocation of the object's destructor when you invoke delete on objects with a destroying operator delete function. The (still live) object is then passed to the destroying operator delete, so that it can do the deallocation and destruction business.

Its purpose is not to make the statement delete whatever; lie to the user about what this statement accomplishes. But as a consequence of one of the use cases of the feature (virtual destructors without virtual functions), the feature can be (ab)used to lie to the user.

The lifetime of an object ends when its destructor is entered (or when the storage is reused/released). If a destroying operator delete is (ab)used to prevent calling that destructor, then deleteing the object will not end its lifetime.

But lying to the user is a bad idea and you shouldn't do it.

Related