20

Suppose I have a pointer to a dynamically allocated array of 10 elements:

T* p = new T[10];

Later, I want to release that array:

delete[] p;

What happens if one of the T destructors throws an exception? Do the other elements still get destructed? Will the memory be released? Will the exception be propagated, or will program execution be terminated?

Similarly, what happens when a std::vector<T> is destroyed and one of the T destructors throws?

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • 11
    Your destructors *really* shouldn't be throwing exceptions. http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.9 – tjm Jun 28 '11 at 13:03
  • Letting exceptions escape destructor may result with `terminate` called. – Tadeusz Kopec for Ukraine Jun 28 '11 at 13:03
  • 1
    Of course, destructors that emit exceptions are pathological. – Fred Larson Jun 28 '11 at 13:03
  • 3
    @tjm: I am perfectly aware that destructors should not throw, and as long as I write `T`, I can ensure that. However, I'm interested in the semantics of `delete[]`, because I need to perfectly emulate them in my own container classes which use allocators, placement-new and co. – fredoverflow Jun 28 '11 at 15:59
  • 1
    @FredOverflow That's cool. I didn't mean to offend and apologize if I did. I think it's a good question and have been monitoring it for *real* answers, instead of all those that have just repeated what I said as a comment. I left it as a comment, not an answer, exactly because I knew it didn't answer the question. I just thought it was important to have it said in a question like this. – tjm Jun 28 '11 at 16:05
  • 1
    C++11 trivia: destructors are implicitly `noexcept(true)`, so it is perfectly reasonable to simply not allow throwing destructors at all [via `std::is_nothrow_destructible`] in your custom containers. Then, no matter how `delete[] p` is defined, you're still guarenteed that the program will terminate if it fails. Obviously, this depends on your project targetting C++11, and does not answer your question in the slightest. – Dennis Zickefoose Jun 28 '11 at 16:26

6 Answers6

6

I can not see it explicitly called out in the standard:

Just that they will be called in reverse order of creation

5.3.5 Delete [expr.delete]

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).

And that the memory deallocation will be done even if the exception is thrown:

7 If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will 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 ]

I tried the following code in G++ and it shows that that no more destructors get called after the exception:

#include <iostream>
int id = 0;
class X
{
    public:
         X() {   me = id++; std::cout << "C: Start" << me << "\n";}
        ~X() {   std::cout << "C: Done " << me << "\n";
                 if (me == 5) {throw int(1);}
             }
    private:
        int me;
};

int main()
{
    try
    {
        X       data[10];
    }
    catch(...)
    {
        std::cout << "Finished\n";
    }
}

Execute:

> g++ de.cpp
> ./a.out
C: Start0
C: Start1
C: Start2
C: Start3
C: Start4
C: Start5
C: Start6
C: Start7
C: Start8
C: Start9
C: Done 9
C: Done 8
C: Done 7
C: Done 6
C: Done 5
Finished

Which all leads back to this (very old answer):
throwing exceptions out of a destructor

Community
  • 1
  • 1
Martin York
  • 257,169
  • 86
  • 333
  • 562
5

Never do that. If there is already an active exception, std::terminate will be called: "Bang, you're dead". Your destructor must. Not. Throw. Resist.


edit: Relevant section from the Standard (14882 2003), 15.2 Constructors and Destructors [except.dtor] :

15.2.3 The process of calling destructors for automatic objects constructed on the path from a try block to a throw-expression is called “stack unwinding.” [Note: If a destructor called during stack unwinding exits with an exception, terminate is called (15.5.1). So destructors should generally catch exceptions and not let them propagate out of the destructor. —end note]


Testcase for playing around (in real life, throw something that is derived from std::exception, never throw int or something else!):

    #include <iostream>
    int main() {
        struct Foo {
            ~Foo() {
                throw 0; // ... fore, std::terminate is called.
            }
        };

        try {
            Foo f;
            throw 0; // First one, will be the active exception once Foo::~Foo()
                     // is executed, there- ...
        } catch (int) {
            std::cout << "caught something" << std::endl;
        }
    }
Sebastian Mach
  • 38,570
  • 8
  • 95
  • 130
  • 2
    I believe this only happens if the destructor is called while the stack is being unwound due to another exception. But of course it is a great argument not to use exceptions in destructors. – Alexander Gessler Jun 28 '11 at 13:05
  • 1
    Although the answer is correct in the given circumstances, it makes some overly generic statements: there is at least one special idiom where it may make sense to throw in a destructor, and it's also a good idea to establish the convention that `main` catches `int` and returns with its value, then use `throw someInt;` rather than `exit(someInt)` to quit the application. – James Kanze Jun 28 '11 at 14:21
  • @James Kanze: What would that idiom be? And how does this idiom defend before an active exception? – Sebastian Mach Jun 28 '11 at 14:28
  • 2
    This does not answer the question. "If there is already an active exception": but there is not! And `delete[] p` in normal code (that is, outside of a destructor) has nothing to do with stack unwinding. – fredoverflow Jun 28 '11 at 15:53
  • @James Kanze: If there is I have no heard of it. You should **NEVER** throw from a destructor. In fact destructor should be written to catch and discard all exceptions thrown. – Martin York Jun 28 '11 at 16:43
  • 4
    @phresnel: All true and exactly why destructors should not throw exceptions, but does not answer the question. – Martin York Jun 28 '11 at 16:45
  • @phresnel You use a temporary to build up the message string for the exception; the exception is actually thrown at the end of the full expression, when the temporary is destructed. Ideally, the destructor should check `uncaught_exception()` before throwing, but in practice, it's possible to write the collector so that the only possible exception during collection is `bad_alloc`, and if you've installed a custom new handler, you can avoid that one as well. – James Kanze Jun 28 '11 at 17:00
5

5.3.5.7 If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will call a deallocation function (3.7.3.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 ]

Couldn't find anything about destructors except for

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).

I guess that after throwing no more destructors are called, but I'm not sure.

Tadeusz Kopec for Ukraine
  • 12,283
  • 6
  • 56
  • 83
  • +1 for answering the question, instead of answering something else that you thought the question should have been, like "should I write destructors that throw?" :-) – Steve Jessop Jun 28 '11 at 15:22
  • +1 This seems to be the only answer so far that even tries to answer my question. – fredoverflow Jun 28 '11 at 15:56
  • 1
    Logically, no more destructors would be called... the thrown exception would have to be saved somehow, and rethrown after the loop, but what if another destructor throws? This isn't a case where `terminate` is called for [the first exception was caught and handled, after all], but there are now two exceptions that both need to propagate up and out. Just bailing is the more reasonable solution. But is that codified in the standard? Not sure. – Dennis Zickefoose Jun 28 '11 at 16:14
  • @Dennis: it seems we're lacking something here :/ – Matthieu M. Jun 28 '11 at 17:15
  • If one dtor in an array throws, the deallocation function can ___not___ be called because some of the dtors haven't run yet, and you can't delete the memory of objects that hadn't been destructed. So the memory cannot be freed, which directly contradicts the standard. IOW, that quote from the standard is useless. (As is the other one, because the order of destruction is completely irrelevant here.) – sbi Jun 28 '11 at 21:54
2

To answer your second question, if you used std::vector instead, there wouldn't be any need for a call to delete, you're not using pointers (the vector class is internally I believe, but this is not up to you to manage).

MGZero
  • 5,812
  • 5
  • 29
  • 46
2

Okay, here is some experimental code:

#include <cstddef>
#include <cstdlib>
#include <new>
#include <iostream>

void* operator new[](size_t size) throw (std::bad_alloc)
{
    std::cout << "allocating " << size << " bytes" << std::endl;
    return malloc(size);
}

void operator delete[](void* payload) throw ()
{
    std::cout << "releasing memory at " << payload << std::endl;
    free(payload);
}

struct X
{
    bool throw_during_destruction;

    ~X()
    {
        std::cout << "destructing " << (void*)this << std::endl;
        if (throw_during_destruction) throw 42;
    }
};

int main()
{
    X* p = new X[10]();
    p[5].throw_during_destruction = true;
    p[1].throw_during_destruction = true;
    delete[] p;
}

Running the code gave the following output on g++ 4.6.0:

allocating 14 bytes
destructing 0x3e2475
destructing 0x3e2474
destructing 0x3e2473
destructing 0x3e2472
destructing 0x3e2471
terminate called after throwing an instance of 'int'

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

So it would seem that std::terminate is called immediately as soon as the first destructor throws. The other elements are not destructed, and the memory is not released. Can anyone confirm this?

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • As I recall, the behavior of a program that allows an exception to propagate out of `main` is somewhat less well defined... however, according to ideone catching the `int` still results in the memory not being deallocated. http://ideone.com/9orU3 Curious. – Dennis Zickefoose Jun 28 '11 at 18:28
0

If an exception is thrown, it is thrown. The object that failed to destruct is obviously not properly destroyed, and neither are the ones remaining in the array.

If you use a vector, the problem is the same, just not in your code. :-)

So, throwing destructors is just a Bad Idea(tm).


Like @Martin shows below, the object that did thrown is formally non-existent as soon as we enter the destructor. The others might have their memory reclaimed as well.

However, it obviously contained some complicated things that were not properly of flusher flushed. If that object, and the others following it in the array, contained some mutex locks, open files, database caches, or shared_ptrs, and none of those had their destructors run, we are likely in BIG trouble.

Having std::terminate called at that point, to put the program out of its misery, seems like something you would wish for!

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • If you throw out of the destructor the object is still destroyed (ie unusable and considered unusable). This is because the deallocation function for the memory is called weather an exception is thrown or not (see n3242: 5.3.5/7 The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. – Martin York Jun 28 '11 at 16:54
  • @Martin - But it probably didn't successfully release all of its resources, so it is still not properly dead. A Zombie? – Bo Persson Jun 28 '11 at 16:55
  • The memory associated with the object has been released. So the object is gone. Its resource as you say may still be hanging around but that's not the same as the object. – Martin York Jun 28 '11 at 17:07
  • See also: n3242: 12.4/14 Once a destructor is invoked for an object, the object no longer exists; – Martin York Jun 28 '11 at 17:13