【问题标题】:Why is a unique_ptr not freed after a constructor calls an exception?为什么构造函数调用异常后没有释放unique_ptr?
【发布时间】:2015-07-24 05:09:56
【问题描述】:

在以下代码中:

#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 和 ISO C++ 人员herehere,我的理解是堆栈应该在Foo 的构造函数上展开,并且x 应该调用它的析构函数,它应该调用@ 987654330@。当然,这并没有发生,那为什么内存没有被释放呢?

【问题讨论】:

  • @TheParamagneticCroissant Foo 的构造函数抛出异常,而不是 unique_ptr 的构造函数。由于在抛出异常时x 已完全构造,因此应为x 调用unique_ptr 的析构函数。
  • @TheParamagneticCroissant ~Foo() 不会运行,但 ~unique_ptr() 应该 - 不?
  • 不仅如此,因为你没有捕捉到异常,你会因为程序中止而丢失控制台输出?
  • 哦,等等,你throw; 没有一个活跃的例外。那直接到terminate。根本不会发生堆栈展开。
  • 你没有捕捉到异常,所以在释放 unique_ptr 之前执行到terminate()。如果添加 catch 处理程序,您将看到两条消息。

标签: c++ c++11 c++14


【解决方案1】:

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

您的新代码未处理就引发异常。在那种情况下,堆栈是否展开是实现定义的,所以它仍然完全符合terminate()。 [except.terminate],强调我的:

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

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

在这种情况下,std::terminate() 被称为 (18.8.3)。 在这种情况下 如果没有找到匹配的处理程序,它是实现定义的 堆栈是否在std::terminate() 之前展开 调用。 在搜索处理程序的情况下(15.3) 遇到带有 a 的函数的最外层块 noexcept-specification 表示不允许出现异常(15.4),栈是否展开、展开由实现定义 在调用std::terminate() 之前,部分或根本不展开。 在所有其他情况下,堆栈之前不得展开 std::terminate() 被调用。 不允许实现 提前结束堆栈展开基于确定的 展开过程最终会导致调用std::terminate()

【讨论】:

  • @wyer33 如果用户没有捕捉到异常,整个程序就会崩溃烧毁,那你为什么关心内存是否被释放呢?
  • @T.C.好吧,在我的实际情况中,我有一个指向数据库的指针,我想在程序终止之前清理一些条目。通常,我会在 unique_ptr 上使用自定义解除分配器。
  • @wyer33 好的,然后在重新抛出异常之前调用unique_ptr 上的reset() 强制清理。
  • @wyer33 是的,您可以依赖函数try-block。所有完全构造的子对象在进入其处理程序之前都会被销毁。
  • @T.C.谢谢您的帮助!万一有人在看,我相信相关部分是 15.3.11 The fully constructed base classes and members of an object shall be destroyed before entering the handler of a function-try-block of a constructor for that object.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-02
  • 2011-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多