wyer33
wyer33

Reputation: 6340

Why is a unique_ptr not freed after a constructor calls an exception?

In the following code:

#include <memory>
#include <iostream>

void mydeallocator(int * x) {
    std::cerr << "Freeing memory" << std::endl;
    delete x;
}

struct Foo {
    std::unique_ptr <int,std::function <void(int*)>> x;
    Foo(bool fail) : x(new int(1),mydeallocator) {
        if(fail)
            throw std::runtime_error("We fail here");
    }
};

int main() {
    {auto foo1 = Foo(false);}
    {auto foo2 = Foo(true);}
}

It appears that memory is not being deallocated properly when Foo(true) is called. Namely, when we compile and run this program, we have the result:

Freeing memory
terminate called after throwing an instance of 'std::runtime_error'
  what():  We fail here
Aborted

I believe that the message Freeing memory should be called twice. Basically, according to this question and the ISO C++ folks here and here, my understanding is that the stack should unwind on the constructor for Foo and that x should call its destructor, which should call mydeallocator. Certainly, this is not happening, so why is the memory not being freed?

Upvotes: 20

Views: 1866

Answers (1)

T.C.
T.C.

Reputation: 137310

Your original code throw; when you have nothing to rethrow. That causes std::terminate to be called; the stack is not unwound (and hence the destructors don't run).

Your new code throws an exception without handling it. In that case whether the stack is unwound is implementation-defined, so it's still perfectly conforming to terminate() right away. [except.terminate], emphasis mine:

In some situations exception handling must be abandoned for less subtle error handling techniques. [ Note: These situations are:

  • when the exception handling mechanism, after completing the initialization of the exception object but before activation of a handler for the exception (15.1), calls a function that exits via an exception, or
  • when the exception handling mechanism cannot find a handler for a thrown exception (15.3), or
  • when the search for a handler (15.3) encounters the outermost block of a function with a noexcept-specification that does not allow the exception (15.4), or
  • when the destruction of an object during stack unwinding (15.2) terminates by throwing an exception, or
  • when initialization of a non-local variable with static or thread storage duration (3.6.2) exits via an exception, or
  • when destruction of an object with static or thread storage duration exits via an exception (3.6.3), or
  • when execution of a function registered with std::atexit or std::at_quick_exit exits via an exception (18.5), or
  • when a throw-expression (5.17) with no operand attempts to rethrow an exception and no exception is being handled (15.1), or
  • when std::unexpected exits via an exception of a type that is not allowed by the previously violated exception specification, and std::bad_exception is not included in that exception specification (15.5.2), or
  • when the implementation’s default unexpected exception handler is called (D.8.1), or
  • when the function std::nested_exception::rethrow_nested is called for an object that has captured no exception (18.8.6), or
  • when execution of the initial function of a thread exits via an exception (30.3.1.2), or
  • when the destructor or the copy assignment operator is invoked on an object of type std::thread that refers to a joinable thread (30.3.1.3, 30.3.1.4), or
  • when a call to a wait(), wait_until(), or wait_for() function on a condition variable (30.5.1, 30.5.2) fails to meet a postcondition. —end note ]

In such cases, std::terminate() is called (18.8.3). In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called. In the situation where the search for a handler (15.3) encounters the outermost block of a function with a noexcept-specification that does not allow the exception (15.4), it is implementation-defined whether the stack is unwound, unwound partially, or not unwound at all before std::terminate() is called. In all other situations, the stack shall not be unwound before std::terminate() is called. An implementation is not permitted to finish stack unwinding prematurely based on a determination that the unwind process will eventually cause a call to std::terminate().

Upvotes: 21

Related Questions