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?
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
orstd::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()
, orwait_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 beforestd::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 beforestd::terminate()
is called. In all other situations, the stack shall not be unwound beforestd::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 tostd::terminate()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With