我有一个包装一些非托管资源的类。它们在构造函数中分配,并根据RAII模式在析构函数中释放。但是,分配不是原子的,并且,如果构造函数的后期失败,则必须在向调用者抛出异常之前释放在早期阶段分配的所有资源。
这个边缘案例代码越来越像破坏者本身。
我知道C ++类能够调用自己的析构函数,并且因为析构函数被实现为处理通过移动操作处于僵尸状态的实例,所以析构函数已经检查资源是否实际已分配试图释放他们。那么,如果构造失败,类是否可以调用它的析构函数?
另一种方法是创建一个名为release()
或uninitialise()
或类似名称的命名方法,并在构造失败时调用该方法,并作为析构函数的主体。
哪种模式更好?
答案 0 :(得分:5)
比你的任何一个建议更好 - 模式也是为每个成员对象使用RAII,而不仅仅是复合对象。
坚持单一责任原则。一个对象应该只管理单个对象或单个数组的内存。
像你这样的复合对象应该只拥有处理自己内存的RAII对象。如果一个成员的构造成功而另一个成员抛出,那么第一个成员的析构函数将被调用,它将释放它自己的记忆。
此类所有权的标准库中有std::unique_ptr
。
答案 1 :(得分:2)
除了自己实施RAII之外,您还应该使用实现RAII的构造。
所以不要做这样的事情:
class MyClass
{
public:
MyClass()
: buffer_(new char[512])
, resource_(new Resource)
{
}
~MyClass()
{
delete resource_;
delete[] buffer_;
}
private:
char* buffer_;
Resource* resource_;
};
你这样做:
class MyClass
{
public:
MyClass()
: buffer_(512, 0)
, resource_(new Resource)
{
}
private:
// std::vector and std::unique_ptr will free up any resources they
// acquire in their destructors so I don't have to do anything explicit in mine
std::vector<char> buffer_;
std::unique_ptr<Resource> resource_;
};
这样一来,如果构造函数中的任何内容都失败了,那么你就可以确定已经获得的所有先前资源都将被释放。