Why is an overloaded delete not called when an exception is thrown in a destructor?
Asked Answered
N

3

9

I've written the below code which overloads the new and delete operators and throws an exception in the destructor.

When the exception is thrown, why is the code in the delete operator not executed (and "bye" printed)?

If it shouldn't be executed, (how) is the memory freed? Is one of the other delete operators called? Would overloading one of them instead result in the corresponding code being executed? Or is the memory simply not freed because a failed destruction implies that maybe it shouldn't be?

#include <iostream>
using namespace std;
class A
{
public:
    A() { }
    ~A() noexcept(false) { throw exception(); }
    void* operator new (std::size_t count)
    {
        cout << "hi" << endl;
        return ::operator new(count);
    }
    void operator delete (void* ptr)
    {
        cout << "bye" << endl;
        return ::operator delete(ptr);
    }
    // using these (with corresponding new's) don't seem to work either
    // void operator delete (void* ptr, const std::nothrow_t& tag);
    // void operator delete (void* ptr, void* place);
};

int main()
{
    A* a = new A();
    try
    {
        delete a;
    }
    catch(...)
    {
        cout << "eek" << endl;
    }
    return 0;
}

Output:

hi
eek

Live demo.

I looked at:

But I couldn't find an answer to what exactly happens (1) for an exception in the destructor (as opposed to the constructor) and (2) with an overloaded delete.

I don't need a lecture on throwing an exception in a destructor being bad practice - I just ran into similar code and I'm curious about the behaviour.


I would prefer an answer supported by the standard or similar references, if such references exist.

Nutt answered 12/8, 2017 at 20:52 Comment(13)
I've written the below code [...] I just ran into this code ... I'm confused.Adama
Probably wrote the given code as a simplification of something he found on the Internet.Dishwasher
@Adama I ran into similar code, I wrote my own simplification of it.Nutt
@Dukeling Logically, why should your version of delete be called if you've thrown an exception in the destructor of A? The purpose of delete would be to, for example, deallocate resources. Would you want your resources deallocated before ~A() is executed? It would make sense that ~A() executes, and the logic for your delete gets called afterwards. Having said that, letting exceptions escape out of a destructor is not a good idea.Meek
I do get "bye" using g++ 7.1.0 and clang++ 3.8.0. I can't find a gcc bug report describing what changed, though.Novanovaculite
Work with GCC7... but... Does not work in GCC-4.8. Maybe UB?Coincidence
[expr.delete] has "[Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception.]". [except.ctor] has "For an object of class type of any storage duration whose initialization or destruction is terminated by an exception... [Note: If the object was allocated by a new-expression, the matching deallocation function, if any, is called to free the storage occupied by the object.]" I'm having trouble finding the normative wording, though.Novanovaculite
Visual Studio 2015 shows "hi" and "eek".Meek
@Novanovaculite The excerpt from [expr.delete] sounds like an answer. What's wrong with that?Adama
@Meek delete should be called because there isn't much the compiler can do about the destructor failing. Even if all applicable resources aren't freed, letting more memory leak doesn't seem like the best way to handle that. There is a logical argument behind trying to destruct the object again later, but successful destruction may not be possible and that conflicts with the other compilers' behaviour described here and Biagio's answer.Nutt
@Adama Notes are not normative.Poliard
This is core issue 353 and GCC bug 55635.Poliard
@Poliard It makes sense. They are notes.Adama
C
6

The standard Draft N4296 5.3.5, pag 121 says:

[expr.delete] [ Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. — end note ]

So the operator delete has to be called regardeless the destructor throws.

However, as has emerged from the comments, some compilers does not properly call the operator delete. This can be resolved as bug compiler.

Bug tested for:

Coincidence answered 12/8, 2017 at 21:54 Comment(1)
When quoting from the standard, you should give the tag associated with the section you're quoting from ([expr.delete] in this case) since the sections can move around and get renumbered. (This is section 8.3.5, paragraph 7, in N4659).Endorsed
A
2

In the 1998 C++ standard (ISO/IEC 14882 First edition, 1998-09-01) the workings of a delete expression are stated quite simply in "Section 5.3.5 Delete [expr.delete]" in paras 6 and 7.

6 The delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted. In the case of an array, the elements will be destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 12.6.2).

7 The delete-expression will call a deallocation function (3.7.3.2).

In combination, these clauses require that destructor will be invoked (or destructors for an array) and that the deallocation function will be called unconditionally. There is no provision here for not calling the deallocation function if an exception is thrown.

In the 1998 standard, language lawyers and compiler developers will probably take delight in the sophistry of arguing a different interpretation than I've stated above. Fortunately, things are more explicit in later standards...

In Draft N4296 available from open-std.org the same clauses are expanded as follows: (from memory the wording in the official standard is the same, but I don't have a copy on my current machine)
(emphasis mine)

6 If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted. In the case of an array, the elements will be destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 12.6.2).

7 If the value of the operand of the delete-expression is not a null pointer value, then:

(7.1) - If the allocation call for the new-expression for the object to be deleted was not omitted and the allocation was not extended (5.3.4), the delete-expression shall call a deallocation function (3.7.4.2). The value returned from the allocation call of the new-expression shall be passed as the first argument to the deallocation function.

(7.2) - Otherwise, if the allocation was extended or was provided by extending the allocation of another new-expression, and the delete-expression for every other pointer value produced by a new-expression that had storage provided by the extended new-expression has been evaluated, the delete-expression shall call a deallocation function. The value returned from the allocation call of the extended new-expression shall be passed as the first argument to the deallocation function.

(7.3) - Otherwise, the delete-expression will not call a deallocation function (3.7.4.2).

Otherwise, it is unspecified whether the deallocation function will be called. [Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception.end note]

The note at the end spells out that the deallocation function must be called even if the destructor throws an exception.

I'm unsure offhand which evolution of the standard first spelled things out, but based on the above, the clauses will probably remain in Section 5.3.5 (tag [expr.delete]).

Analemma answered 12/8, 2017 at 23:44 Comment(1)
I'd feel better about accepting this answer if you add some explicit explanation for the behaviour I'm seeing (even if it is speculative), e.g. probably a compiler bug or perhaps a result of the initial phrasing not explicitly handling this case.Nutt
O
-1

The destructor is called before calling to the delete operator. See cppreference - delete expression

If expression is not a null pointer, the delete expression invokes the destructor (if any) for the object that's being destroyed, or for every element of the array being destroyed (proceeding from the last element to the first element of the array). After that, unless the matching new-expression was combined with another new-expression (since C++14) the delete expression invokes the deallocation function, either operator delete (for the first version of the expression) or operator delete[] (for the second version of the expression).

Due to this order of operations, the destructor is called and throws an exception before your overloaded version of the delete operator is called.

Ortego answered 12/8, 2017 at 21:39 Comment(4)
I cannot see any reference to throwing destructors, or am I wrong?Coincidence
The reference is not for throwing destructors, but for the order of operations with a delete expression. Due to this order of operations, the destructor is called and throws an exception before your overloaded version of the delete operator is called.Ortego
You're assuming there's (either accidentally or intentionally) no exception handling in the code which does this - that's not stated in the text you quoted.Nutt
cppreference.com is not a normative reference. The paragraph you quote could equally be interpreted to say that deallocation follows destruction unconditionally.Bentinck

© 2022 - 2024 — McMap. All rights reserved.