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

wye*_*r33 20 c++ c++11 c++14

在以下代码中:

#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);}
}
Run Code Online (Sandbox Code Playgroud)

看来,Foo(true)调用时内存没有被正确释放.也就是说,当我们编译并运行该程序时,我们得到了结果:

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

我相信该消息Freeing memory应该被调用两次.基本上,根据这个问题这里这里的ISO C++人员,我的理解是堆栈应该在构造函数上展开,Foo并且x应该调用它的析构函数,它应该调用mydeallocator.当然,这不会发生,为什么内存没有被释放?

T.C*_*.C. 21

throw;没有任何重新抛出时的原始代码.这导致std::terminate被召唤; 堆栈没有解开(因此析构函数不会运行).

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

在某些情况下,必须放弃异常处理以获得不太精细的错误处理技术.[ 注意:这些情况是:

  • 当异常处理机制在完成异常对象的初始化之后但在激活异常处理程序(15.1)之前,调用通过异常退出的函数,或者
  • 当异常处理机制找不到抛出异常(15.3)的处理程序时,或者
  • 当搜索处理程序(15.3)遇到函数的最外面的块时,noexcept-specification不允许异常(15.4),或者
  • 当堆栈展开期间对象的销毁(15.2)通过抛出异常而终止时,或者
  • 当具有静态或线程存储持续时间(3.6.2)的非局部变量的初始化通过异常退出时,或者
  • 当具有静态或线程存储持续时间的对象的销毁通过异常(3.6.3)退出时,或者
  • 当执行通过异常(18.5)注册std::atexitstd::at_quick_exit退出的函数时,或
  • 当没有操作数的throw-expression(5.17)尝试重新抛出异常并且没有处理异常时(15.1),或者
  • std::unexpected通过先前违反的异常规范不允许的类型的异常退出时,std :: bad_exception不包含在该异常规范(15.5.2)中,或者
  • 当调用实现的默认意外异常处理程序(D.8.1)时,或
  • std::nested_exception::rethrow_nested为没有捕获异常的对象调用函数时(18.8.6),或者
  • 当线程的初始函数的执行通过异常(30.3.1.2)退出时,或
  • 在引用std::thread可连接线程(30.3.1.3,30.3.1.4)的类型的对象上调用析构函数或复制赋值运算符时,或者
  • 当对条件变量(30.5.1,30.5.2)的a wait(),wait_until()wait_for()函数的调用无法满足后置条件时.- 结束说明 ]

在这种情况下,std::terminate()被称为(18.8.3).在没有找到匹配处理程序的情况下,无论堆栈是否在std::terminate()被调用之前被展开,它都是实现定义的.在搜索处理程序(15.3)遇到函数的最外层的情况下,其中 noexcept-specification不允许异常(15.4),它是实现定义的,无论堆栈是展开的,是否部分展开,或者不是在std::terminate()被召唤之前完全解开. 在所有其他情况下,堆栈在std::terminate()被调用之前不应解开 .基于确定展开过程最终将导致调用,不允许实现过早地完成堆栈展开std::terminate().

  • @ wyer33是的,你可以依赖function-try-block.在进入其处理程序之前,所有完全构造的子对象都将被销毁. (3认同)
  • @ wyer33好了,然后在`unique_ptr`上调用`reset()`强制清理,然后再重新抛出异常. (2认同)
  • @TC感谢您的帮助!如果其他人正在查找,我相信相关部分是15.3.11`在进入该对象的构造函数的函数try-block的处理程序之前,应该销毁完全构造的基类和对象的成员. (2认同)