堆栈展开如何处理析构函数调用?

时间:2017-08-20 21:33:54

标签: c++

让我们假设以下简单示例:

#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的资源成员被正确释放?

2 个答案:

答案 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的类型。 (注意 - 这是一个说明性的伪代码,有几种方法可以解决这个问题。)