为什么在构造函数调用异常后 unique_ptr 没有被释放?

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

在下面的代码中:

#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);}
}

调用 Foo(true) 时,似乎内存未正确释放。也就是说,当我们编译 运行 这个程序时,我们得到的结果是:

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

我认为消息 Freeing memory 应该被调用两次。基本上,根据这个 question and the ISO C++ folks here and here,我的理解是堆栈应该在 Foo 的构造函数上展开并且 x 应该调用它的析构函数,后者应该调用 mydeallocator。当然,这并没有发生,那么为什么内存没有被释放?

你的原始代码 throw; 当你没有什么可以重新抛出时。这导致 std::terminate 被调用;堆栈没有展开(因此析构函数没有 运行)。

您的新代码抛出异常但未进行处理。在那种情况下,堆栈是否展开是实现定义的,因此它仍然完全符合 terminate() 的要求。 [except.terminate],强调我的:

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