堆栈展开如何与析构函数调用有关?

How does stack unwinding work regarding destructor calls?

本文关键字:函数调用 析构 堆栈      更新时间:2023-10-16

让我们假设以下简单的例子:

#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析构函数中释放的任何资源分配,这意味着什么?

例如

struct bar {
CostlyResource cr;
bar() {
cr.Open(); // Aquire resource
// something else throws ...
throw -1;
}
~bar() {
if(cr.IsOpen()) {
cr.Release(); // Free resource
}
}
};

为了异常安全实现,我可以做些什么来确保正确释放bar的资源成员?

为了异常安全实现,我该怎么做才能确保 bar 的资源成员被正确释放?

您可以在构造函数中catch、处理和重新抛出匿名 excp:

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
}
}
};

请在此处查看完整的示例代码。


由于这是在构造函数中完成的动态内存分配经常要求的问题,例如

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]>(。

对象的生存期在构造函数完成之前不会开始。 如果从构造函数引发异常,则不会调用该对象的析构函数。当然,任何已经构造的子对象都将以与构造相反的顺序被破坏,就像您在第一个示例中看到的那样,~foo()出现。

第二个示例中的代码不是异常安全的。CostlyResource设计不佳 - 它自己的析构函数应该释放资源。那么你的代码就是正确的。

如果你必须使用一个不能正确清理自己的现有类,那么你应该做一个包装器,例如:

struct SafeCostlyResource : CostlyResource
{
~SafeCostlyResource()
{
if (IsOpen()) 
Release(); 
}
};

并将其用作cr的类型。 (注意 - 这是说明性的伪代码,有几种方法可以解决这个问题(。