20

假设我有一个指向动态分配的 10 个元素的数组的指针:

T* p = new T[10];

稍后,我想释放该数组:

delete[] p;

如果其中一个T析构函数抛出异常会发生什么?其他元素还会被破坏吗?内存会被释放吗?异常会被传播,还是会终止程序执行?

同样,当 astd::vector<T>被销毁并且其中一个T析构函数抛出时会发生什么?

4

6 回答 6

6

我看不到它在标准中明确指出:

只是它们将按创建的相反顺序调用

5.3.5 删除[expr.delete]

6 如果 delete-expression 的操作数的值不是空指针值,则 delete-expression 将为要删除的对象或数组元素调用析构函数(如果有)。在数组的情况下,元素将按照地址递减的顺序被销毁(即,按照它们的构造函数完成的相反顺序;参见 12.6.2)。

即使抛出异常,也会完成内存释放:

7 如果 delete-expression 的操作数的值不是空指针值,则 delete-expression 将调用释放函数 (3.7.4.2)。否则,未指定是否将调用释放函数。[注意:无论对象的析构函数还是数组的某些元素是否抛出异常,都会调用释放函数。——尾注]

我在 G++ 中尝试了以下代码,它表明在异常之后不再调用析构函数:

#include <iostream>
int id = 0;
class X
{
    public:
         X() {   me = id++; std::cout << "C: Start" << me << "\n";}
        ~X() {   std::cout << "C: Done " << me << "\n";
                 if (me == 5) {throw int(1);}
             }
    private:
        int me;
};

int main()
{
    try
    {
        X       data[10];
    }
    catch(...)
    {
        std::cout << "Finished\n";
    }
}

执行:

> g++ de.cpp
> ./a.out
C: Start0
C: Start1
C: Start2
C: Start3
C: Start4
C: Start5
C: Start6
C: Start7
C: Start8
C: Start9
C: Done 9
C: Done 8
C: Done 7
C: Done 6
C: Done 5
Finished

这一切都回到了这个(非常古老的答案):
从析构函数中抛出异常

于 2011-06-28T17:05:29.233 回答
5

永远不要那样做。如果已经有一个活跃的异常,std::terminate将会调用:“Bang, you're dead”。你的析构函数必须。不是。扔。抵抗。


编辑:标准(14882 2003)中的相关部分,15.2构造函数和析构函数[except.dtor]

15.2.3 为在从 try 块到 throw 表达式的路径上构造的自动对象调用析构函数的过程称为“堆栈展开”。[注意:如果在堆栈展开期间调用的析构函数因异常退出,则调用终止 (15.5.1)。所以析构函数通常应该捕获异常,而不是让它们传播出析构函数。——尾注]


用于玩耍的测试用例(在现实生活中,抛出源自 的东西std::exception,永远不要抛出 int 或其他东西!):

    #include <iostream>
    int main() {
        struct Foo {
            ~Foo() {
                throw 0; // ... fore, std::terminate is called.
            }
        };

        try {
            Foo f;
            throw 0; // First one, will be the active exception once Foo::~Foo()
                     // is executed, there- ...
        } catch (int) {
            std::cout << "caught something" << std::endl;
        }
    }
于 2011-06-28T13:03:49.230 回答
5

5.3.5.7 如果删除表达式的操作数的值不是空指针值,删除表达式将调用释放函数(3.7.3.2)。否则,未指定是否将调用释放函数。[注意:无论对象的析构函数还是数组的某些元素是否抛出异常,都会调用释放函数。——尾注]

找不到关于析构函数的任何信息,除了

在数组的情况下,元素将按照地址递减的顺序被销毁(即,按照它们的构造函数完成的相反顺序;参见 12.6.2)。

我想在抛出后不再调用析构函数,但我不确定。

于 2011-06-28T13:23:33.460 回答
2

要回答您的第二个问题,如果您使用 std::vector 代替,则不需要调用删除,您没有使用指针(我相信 vector 类在内部,但这不取决于您管理)。

于 2011-06-28T13:03:56.827 回答
2

好的,这是一些实验代码:

#include <cstddef>
#include <cstdlib>
#include <new>
#include <iostream>

void* operator new[](size_t size) throw (std::bad_alloc)
{
    std::cout << "allocating " << size << " bytes" << std::endl;
    return malloc(size);
}

void operator delete[](void* payload) throw ()
{
    std::cout << "releasing memory at " << payload << std::endl;
    free(payload);
}

struct X
{
    bool throw_during_destruction;

    ~X()
    {
        std::cout << "destructing " << (void*)this << std::endl;
        if (throw_during_destruction) throw 42;
    }
};

int main()
{
    X* p = new X[10]();
    p[5].throw_during_destruction = true;
    p[1].throw_during_destruction = true;
    delete[] p;
}

运行代码在 g++ 4.6.0 上给出以下输出:

allocating 14 bytes
destructing 0x3e2475
destructing 0x3e2474
destructing 0x3e2473
destructing 0x3e2472
destructing 0x3e2471
terminate called after throwing an instance of 'int'

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

So it would seem that std::terminate is called immediately as soon as the first destructor throws. The other elements are not destructed, and the memory is not released. Can anyone confirm this?

于 2011-06-28T17:50:58.747 回答
0

如果抛出异常,则抛出异常。未能销毁的对象显然没有正确销毁,数组中剩余的对象也没有。

如果您使用向量,问题是一样的,只是不在您的代码中。:-)

因此,抛出析构函数只是一个坏主意(tm)。


就像下面的@Martin 所示,一旦我们进入析构函数,抛出的对象就正式不存在了。其他人的记忆也可能被回收。

但是,它显然包含了一些复杂的东西,这些东西不是flusher flusher的正确方法。如果该对象以及数组中跟随它的其他对象包含一些互斥锁、打开的文件、数据库缓存或 shared_ptrs,而这些对象都没有运行它们的析构函数,那么我们可能会遇到大麻烦。

在那时调用 std::terminate 以使程序摆脱困境,这似乎是您所希望的!

于 2011-06-28T13:22:17.760 回答