【问题标题】:Why aren't destructors called when exception isn't caught within main?当 main 中没有捕获到异常时,为什么不调用析构函数?
【发布时间】:2012-01-23 02:42:09
【问题描述】:

我有以下代码:

#include <iostream>
#include <vector>
#include <tr1/memory>

struct FooError {};

struct Foo
{
    ~Foo() { std::cerr << "~Foo() executed" << std::endl; }
    explicit Foo(unsigned int index) { if (5 == index) throw FooError(index); };
};


int main() {
    typedef std::tr1::shared_ptr<Foo> FooPtr;
    std::vector<FooPtr> foos;
    for (unsigned int index = 0; index < 20; ++index)
    {
        try
        {
            foos.push_back(FooPtr(new Foo(index)));
        }
        catch (const FooError&)
        {
            std::cerr << "FooError caught" << std::endl;
        }
    }
}

当有try{} catch{} 块时,我看到一组~Foo() 被执行。当没有异常处理程序时,不会打印任何内容。这是否意味着在处理异常时调用堆栈分配对象的析构函数?还是因为 std::cerr 缓冲问题而没有打印任何内容?

【问题讨论】:

    标签: c++ exception destructor stack-unwinding


    【解决方案1】:

    以下是 C++03 标准的详细信息。

    • 从 15.3/9 开始处理异常

      如果在程序中没有找到匹配的处理程序,则调用函数 terminate();

    • 从 18.6.3 异常终止:

      实现的默认 terminate_handler 调用 abort()。

    • 从 3.6.3/4 终止:

      调用&lt;cstdlib&gt; 中声明的函数void abort(); 会终止程序,而不会为自动或静态存储持续时间的对象执行析构函数,也不会调用传递给 atexit() 的函数。

    这就是为什么你的foos 对象没有被破坏(它具有静态存储持续时间)。但是,即使您将其更改为局部变量(具有自动持续时间),也可能无法解决问题(已添加重点):

    所以对于static duration 对象,除非您更改终止处理程序(也许让它调用exit() 而不是abort()),否则不会调用析构函数。但是,对于自动对象,仍然存在一个可能的问题(已添加重点):

    15.5.1/1 terminate() 函数

    在没有找到匹配处理程序的情况下,它是 实现定义堆栈之前是否展开 调用终止()。在所有其他情况下,堆栈不得 在调用 terminate() 之前展开。

    【讨论】:

    • 我不认为“foos”确实有静态存储持续时间,我认为定义在main函数中。
    • @Steve - 我认为你是对的,这是我的误读。我已经更新了答案。
    【解决方案2】:

    程序的作用域展开,无论是通过正常执行还是通过 try/throw/catch,只有在应用程序从 main 返回退出时才会发生。如果应用程序通过异常退出(或通过abort()terminate()),则不会发生展开,也不会调用析构函数。

    这适用于自动和静态对象。

    【讨论】:

    • 如果我们在它发生的点和main() 返回点之间捕获一个异常,但是然后 rethrow 它,析构函数不会被调用到该点我们在哪里发现了异常?
    • “没有展开” - 通常它是由实现定义的,无论它是否发生,在这种情况下不会。
    • @SteveJessop:你能检查一下这个例子吗:ideone.com/X7Hv8?这是否意味着它的实现定义了是否会调用 foo 的构造函数?还是foo 会一直被破坏?
    • @jrok: 标准中的 15.2 说,“当控制从 throw 表达式传递到处理程序时,自进入 try 块以来构造的所有自动对象都会调用析构函数”,所以 foo进入处理程序时将被破坏。重新抛出将不会被处理,因此是否会调用 B 的 dtor 由实现定义。
    【解决方案3】:

    在循环之后,程序退出之前(从向量)调用析构函数。

    如果你没有捕捉到异常,就会调用 terminate 来中止程序而不调用析构函数。

    【讨论】:

    • “不调用析构函数”——这就是这个实现中发生的事情,但不能保证它们不会被调用(C++11 中的 15.3/9)。
    【解决方案4】:

    如果您捕获到异常,将调用释放器来清理内存。如果你没有捕捉到异常,应用程序就会退出。

    顺便说一下,一个vector实际上把它所有的数据都存储在了堆上;这就是它可以调整大小的原因。您可以将堆栈上的数据视为指向堆上内存的指针(对您隐藏)。

    【讨论】:

    • +1。我没有意识到 OP 是在谈论没有 try/catch 时的情况,以为是关于没有抛出异常的情况。
    • 但是我现在删除的答案的剩余部分仍然很重要 - Foos 不是堆栈分配的。
    • 我想知道为什么没有发生堆栈展开。如果有多个块级别并且在较低的级别上发现错误,我会因为 FooPtr 的 aint 被释放而导致内存泄漏吗?
    • 我一直认为,当引发异常时,它会上升到程序并展开堆栈,在每个子堆栈上为每个堆栈分配的对象调用展开析构函数。
    • 如果捕捉到异常,将调用解构函数。如果你没有抓住应用程序将退出并转到操作系统。如果应用程序退出,操作系统将回收所有内存,因此不会发生内存泄漏。但问题是,如果您的解构器执行了诸如释放数据库锁之类的操作,它将不会被调用。操作系统不会调用解构器(它不知道它们)它只是回收内存。
    猜你喜欢
    • 2023-03-17
    • 2011-11-28
    • 2022-01-06
    • 1970-01-01
    • 2013-09-28
    • 1970-01-01
    • 2012-08-14
    • 1970-01-01
    • 2017-06-18
    相关资源
    最近更新 更多