让我们假设以下简单示例:
#include <iostream>
struct foo {
~foo() {
std::cout << "~foo()" << std::endl;
}
};
struct bar {
foo x;
bar() : x() {
throw -1;
}
~bar() {
std::cout << "~bar()" << std::endl;
}
};
struct baz {
~baz() {
std::cout << "~baz()" << std::endl;
}
};
int main() {
try {
baz y;
bar z;
} // Destructor is called for everything fully constructed up to here?
catch(...) {
}
}
输出
~foo()
~baz()
所以很明显bar
的析构函数不会被调用。
对于旨在在bar
的析构函数中发布的任何类型的资源分配,这意味着什么?
E.g。
struct bar {
CostlyResource cr;
bar() {
cr.Open(); // Aquire resource
// something else throws ...
throw -1;
}
~bar() {
if(cr.IsOpen()) {
cr.Release(); // Free resource
}
}
};
为了异常安全的实现,我该怎么做才能确保bar
的资源成员被正确释放?
答案 0 :(得分:2)
为了实现异常安全,我该怎么做才能确保bar的资源成员被正确释放?
你可以在构造函数中catch
处理并重新抛出一个匿名的excption:
struct bar {
CostlyResource cr;
bar() {
try { // Wrap the whole constructor body with try/catch
cr.Open(); // Aquire resource
// something else throws ...
throw -1;
}
catch(...) { // Catch anonymously
releaseResources(); // Release the resources
throw; // Rethrow the caught exception
}
}
~bar() {
releaseResources(); // Reuse the code ro release resources
}
private:
void releaseResources() {
if(cr.IsOpen()) {
cr.Release(); // Free resource
}
}
};
请参阅完整示例代码here。
因为这是在
等构造函数中经常要求进行动态内存分配的问题class MyClass {
TypeA* typeAArray;
TypeB* typeBArray;
public:
MyClass() {
typeAAArray = new TypeA[50];
// Something else might throw here
typeBAArray = new TypeB[100];
}
~MyClass() {
delete[] typeAAArray;
delete[] typeBAArray;
}
};
最简单的方法是使用适当的容器(例如std::vector<TypeA>
,std::vector<TypeB>
)或智能指针(例如std::unique_ptr<TypeA[50]>
)。
答案 1 :(得分:1)
在构造函数完成之前,对象的生命周期才会开始。如果从构造函数中抛出异常,则不会调用该对象的析构函数。当然,任何已经构建的子对象都将按照构造的相反顺序被破坏,正如您在第一个示例中看到~foo()
出现的那样。
第二个示例中的代码不是异常安全的。 CostlyResource
设计很差 - 它自己的析构函数应该释放资源。那么你的代码就是正确的。
如果你必须使用一个不能正确清理它的现有类,那么你应该制作一个包装器,例如:
struct SafeCostlyResource : CostlyResource
{
~SafeCostlyResource()
{
if (IsOpen())
Release();
}
};
并将其用作cr
的类型。
(注意 - 这是一个说明性的伪代码,有几种方法可以解决这个问题。)