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

1

4

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());
}
Ruminate answered 6/3, 2021 at 23:46 Comment(0)
D
4

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.

Disjunction answered 7/3, 2021 at 0:19 Comment(5)
And overriding regular virtual functions is not 'lying' in exactly the same way? The user shouldn't care if the object is 'really' destroyed. From their perspective they aren't allowed to dereference whatever after delete whatever; in either case. It's just abstraction.Ruminate
@yurikilochek: "And overriding regular virtual functions is not 'lying' in exactly the same way?" No. By declaring a function virtual, you are stating the intent for it to be overridden. By overriding it, you're stating the intent to replace the base class version. In all cases, the intent from all parties involved is clear There is no perfidy the way there is when you make delete whatever; not actually destroy whatever.Disjunction
@yurikilochek: "It's just abstraction." It's a bad abstraction that twists the meaning of obvious code to be something it isn't. If you want an interface where you get to use an object for a while and then return it to be used at some later date, then make that interface. Don't refashion delete into that mechanism just because it's technically possible. If you want to use smart pointers with such types, you can provide deleters that will call your returning API rather than overriding the behavior of basic syntactic constructs like delete.Disjunction
this question is tagged language lawyer. The answer spends a lot of time moralizing if the OP should, and no time citing the standard. This seems a poor fit for such a tagged question? It does say "yes" to if itnis legal, based in the claim that delete ending lifetime of the object is a (only) function of calling the destructor, so there is that I guess?Dialyse
@Yakk-AdamNevraumont: "no time citing the standard" It's permitted because there's nothing in the standard that forbids it, and you can't cite the lack of something being forbidden. I mean, I could copy/paste from the section about lifetime ending, but I don't see how that materially affects anything.Disjunction

© 2022 - 2024 — McMap. All rights reserved.