Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c++11

c++14

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?

like image 310
wyer33 Avatar asked Jul 24 '15 05:07

wyer33


1 Answers

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

like image 64
T.C. Avatar answered Oct 11 '22 23:10

T.C.