T* p = new T[10];
稍后,我想释放那个数组:
delete[] p;
如果一个 T
析构函数抛出异常会发生什么?其他元素是否仍然被销毁?内存是否会被释放?异常是否会传播,还是程序执行将被终止?
同样地,当 std::vector<T>
被销毁且其中一个 T
的析构函数抛出时会发生什么?
T* p = new T[10];
稍后,我想释放那个数组:
delete[] p;
如果一个 T
析构函数抛出异常会发生什么?其他元素是否仍然被销毁?内存是否会被释放?异常是否会传播,还是程序执行将被终止?
同样地,当 std::vector<T>
被销毁且其中一个 T
的析构函数抛出时会发生什么?
标准中没有明确说明:
只是说它们将按创建的相反顺序被调用
6 如果 delete-expression 的操作数的值不是空指针值,则 delete-expression 将调用要删除的对象或数组元素的析构函数(如果有)。在数组的情况下,元素将按地址递减的顺序销毁(也就是说,按照其构造完成的相反顺序;参见 12.6.2)。
内存释放将会进行即使抛出异常:
7 如果 delete-expression 的操作数的值不是空指针值,则 delete-expression 将调用一个 deallocation function (3.7.4.2)。否则,未指定是否将调用 deallocation function。[注意:不管对象或某个数组元素的析构函数是否抛出异常,都会调用 deallocation function。—end note]
我在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
std::terminate
:"Bang, you're dead"。你的析构函数必须不抛出异常。抵制诱惑。
[except.dtor]
:
15.2.3 从 try 块到 throw 表达式路径上构造的自动对象的析构过程称为“堆栈展开”。[注意:如果在堆栈展开期间调用的析构函数退出时引发异常,则会调用terminate(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;
}
}
main
捕获int
并返回其值,然后使用 throw someInt;
而不是 exit(someInt)
来退出应用程序也是一个好主意。 - James Kanzedelete[] p
与堆栈展开无关。 - fredoverflowterminate
的情况[毕竟第一个异常已被捕获和处理],但现在有两个异常都需要传播并退出。直接退出可能是更合理的解决方案,但它是否被规范化呢?还不确定。 - Dennis Zickefoose好的,这里是一些实验性代码:
#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;
}
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.
看起来,当第一个析构函数抛出异常时,std::terminate
会立即被调用。其他元素不会被销毁,内存也不会释放。有人可以确认这一点吗?
main
外的程序行为有点不太明确... 但是,根据 ideone 的说法,捕获 int
仍然导致内存未被释放。http://ideone.com/9orU3。好奇。 - Dennis Zickefoose回答你的第二个问题,如果你使用std::vector,就不需要调用delete了,因为你不使用指针(尽管vector类在内部可能使用指针,但这不是由你来管理的)。
如果抛出异常,那么就会抛出异常。明显未能成功销毁的对象也不会被正确地销毁,数组中剩余的对象也是如此。
如果您使用向量,则问题相同,只是不在您的代码中。 :-)
因此,抛出析构函数只是一个坏主意(tm)。
就像@Martin所展示的,一旦我们进入析构函数,抛出异常的对象就正式不存在了。其他对象也可能被回收。
然而,显然该对象包含了一些复杂的东西,这些东西并没有得到适当的刷新处理。如果该对象及其后面的其他对象包含互斥锁、打开的文件、数据库缓存或shared_ptrs,并且这些对象中没有一个执行了它们的析构函数,那么我们可能会遇到大麻烦。
在此时调用std::terminate,以结束程序的痛苦,似乎是您希望的事情!
terminate
。 - Tadeusz Kopec for UkraineT
,我就能确保这一点。但是,我对delete[]
的语义感兴趣,因为我需要在我自己定义的容器类中完美模拟它们,其中包括使用分配器、定位new等。 - fredoverflownoexcept(true)
,因此在自定义容器中完全可以不允许抛出析构函数[通过std::is_nothrow_destructible
]。这样,无论如何定义delete[] p
,如果失败,程序仍然保证终止。显然,这取决于您的项目是否针对C++11,并且根本没有回答您的问题。 - Dennis Zickefoose