在以下代码中:
#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::atexit
或std::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()
的调用。