我定义了一个类foo如下:
class foo {
private:
static int objcnt;
public:
foo() {
if(objcnt==8)
throw outOfMemory("No more space!");
else
objcnt++;
}
class outOfMemory {
public:
outOfMemory(char* msg) { cout << msg << endl;}
};
~foo() { cout << "Deleting foo." << endl; objcnt--;}
};
int foo::objcnt = 0;
这是主要功能:
int main() {
try {
foo* p = new foo[3];
cout << "p in try " << p << endl;
foo* q = new foo[7];
}catch(foo::outOfMemory& o) {
cout << "Out-of-memory Exception Caught." << endl;
}
}
很明显,行"foo*q=new foo[7];"只成功创建了5个对象,在第6个对象上抛出了内存不足异常。但事实证明,只有5个析构函数调用,并且存储在p指向的位置的3个对象的数组没有调用destcurator。所以我想知道为什么?为什么程序只调用这5个对象的析构函数?
"原子"C++分配和构造函数是正确且异常安全的:If new T
;抛出,没有任何泄漏,如果new T[N]
在过程中的任何地方抛出,则已经构建的所有内容都将被销毁。所以没什么好担心的。
现在离题:
始终必须担心的是在任何单个责任单元中使用多个new
表达式。基本上,你必须将任何new
表达视为一个烫手山芋,需要被一个完全构建的、负责任的守护对象所吸收。
严格地将new
和new[]
视为库构建块:您永远不会在高级用户代码中使用它们(可能除了构造函数中的单个new
之外(,并且只在库类中使用它们。
也就是说:
// BAD:
A * p = new A;
B * q = new B; // Ouch -- *p may leak if this throws!
// Good:
std::unique_ptr<A> p(new A);
std::unique_ptr<B> q(new B); // who cares if this throws
std::unique_ptr<C[3]> r(new C[3]); // ditto
另一方面:标准库容器实现了类似的行为:如果你说resize(N)
(增长(,并且在任何的构造过程中发生异常,那么所有已经构造的元素都会被破坏。也就是说,resize(N)
要么将容器增长到指定的大小,要么根本不增长(例如,在GCC 4.6中,有关异常检查范围构造的库版本,请参阅bits/vector.tcc
中_M_fill_insert()
的实现。(
只有完全构造的对象才会调用析构函数,这些对象的构造函数是正常完成的。只有当new[]
正在进行时抛出异常时,才会自动发生这种情况。因此,在您的示例中,将为q = new foo[7]
运行期间完全构建的五个对象运行析构函数。
由于p
指向的数组的new[]
成功完成,该数组现在被处理到代码中,C++运行时不再关心它了——除非执行delete[] p
,否则不会运行析构函数。
当您在堆上声明数组时,您会得到预期的行为:
int main()
{
try
{
foo p[3];
cout << "p in try " << p << endl;
foo q[7];
}
catch(foo::outOfMemory& o)
{
cout << "Out-of-memory Exception Caught." << endl;
}
}
在代码中,只有指针是局部自动变量。当堆栈展开时,指针没有任何关联的清理。正如其他人所指出的,这就是为什么C++代码中通常没有RAW指针的原因——它们通常封装在一个类对象中,该类对象使用构造函数/析构函数来控制它们的寿命(智能指针/容器(。
作为旁注。使用std::vector通常比使用原始数组更好(在C++11中,如果您有固定大小的数组,std::array也很有用(。这是因为堆栈的大小有限,而这些对象将大部分数据放在堆中。这些类对象提供的额外方法使它们在代码的其余部分更易于处理,如果您绝对必须有一个旧式数组指针才能传递给C函数,那么很容易获得它们。
int main()
{
try
{
std::vector<foo> p(3);
cout << "p in try " << p << endl;
std::vector<foo> q(7);
// Now you can pass p/q to function much easier.
}
catch(foo::outOfMemory& o)
{
cout << "Out-of-memory Exception Caught." << endl;
}
}