The deleting destructor variant exists so that the syntax
delete ptr;
where ptr
points to an polymorphic type can work even if ptr
doesn't point to the most-derived object. The user of delete ptr;
doesn't know what the offset to the most-derived object or its size is, but it needs to know that in order to call operator delete
correctly. Therefore there needs to be an indirect/virtual call to a function that knows, which is the deleting destructor.
Unfortunately the compiler has to generate the deleting destructor from such a virtual
destructor at least for all classes which are used as most-derived objects, since there could be a delete
expression of that kind in another translation unit that doesn't know about the definition of this most-derived destructor which it must however (indirectly) call.
I don't think that you can separate the delete
behavior of virtual destructors from the ability to do explicit virtual destructor calls and I also don't see any GCC switch to disable the generation of the deleting destructor as a non-standard/ABI conforming option.
So I guess you'll have to avoid virtual destructors. You can get the virtual destruction behavior anyway by forwarding from a virtual
function:
struct Base {
virtual destroy() noexcept { this->~Base(); }
// destructor not virtual
};
struct Derived {
virtual destroy() noexcept override { this->~Derived(); }
};
And then instead of ptr->~Base();
/std::destroy_at(ptr)
you can use ptr->destroy()
.
However, this has the problem that you need to assure that destruct
is properly overriden in every derived class to avoid undefined behavior. CRTP or C++23 explicit object parameters (explicit this
) may help with that.
You also have the problem that someone might accidentally call the destructor directly, again causing undefined behavior. Making the destructor private
is also generally not a solution because the destructor is potentially invoked in many situations, e.g. in a constructor of a class that contains the class in question as a non-static member.
Alternatively I'd suggest defining operator delete(void*, std::size_t)
as inline and a noop in every translation unit. That way GCC seems to at least inline it into the deleting destructor.
You can't delete the operator delete
, because it will be odr-used for the reasons I mentioned above and must therefore viable.
LTO might further help getting rid of unused emitted deleting destructors during linking, at least via devirtualization. But I haven't tested.