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



在以下代码中:

#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应该被调用两次。基本上,根据这个问题和这里和这里的ISO c++人员,我的理解是堆栈应该在Foo的构造函数上展开,x应该调用它的析构函数,它应该调用mydeallocator。当然,这并没有发生,那么为什么内存没有被释放呢?

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

你的新代码抛出了一个异常而没有处理它。在这种情况下,堆栈是否展开是由实现定义的,因此它仍然完全符合terminate()。(除外。终止],强调mine:

在某些情况下,必须放弃异常处理微妙的错误处理技术。[注意:这些情况是:

  • 异常处理机制时,在完成异常对象的初始化之后但在激活之前异常(15.1)的处理程序调用一个函数,该函数通过异常,或
  • 当异常处理机制无法为抛出的异常找到处理程序时(15.3)
  • 当搜索处理程序(15.3)遇到函数的最外层块时,该块具有noexception规范,不允许使用例外(15.4),或
  • 当在堆栈展开(15.2)期间对象的销毁因抛出异常而终止时,或
  • 当具有静态或线程存储持续时间(3.6.2)的非局部变量的初始化通过异常退出时,或
  • 当具有静态或线程存储持续时间的对象的销毁通过异常(3.6.3)退出时,或
  • 当在std::atexitstd::at_quick_exit上注册的函数通过异常退出时(18.5),或
  • 抛出表达式(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)上的wait(), wait_until()wait_for()函数的调用不满足后置条件。- 结束注释]

在这种情况下,调用std::terminate()(18.8.3)。在这种情况下在没有找到匹配处理程序的地方,它是实现定义的是否在std::terminate()之前展开堆栈调用。在搜索处理程序(15.3)的情况下遇到函数的最外层块 noexception -specification不允许异常(15.4),它是实现定义的堆栈是否展开部分展开,或者在调用std::terminate()之前根本不展开。在所有其他情况下,堆栈不应该被展开调用std::terminate()不允许实现提前完成堆栈展开是基于unwind进程最终会导致对std::terminate()的调用。

最新更新