以下示例(未编译,因此我不会保证语法)从资源池中提取两个资源(未使用new
分配),然后在特定事务的持续时间内将它们与MyClass“绑定”在一起
此处由myFunc
实施的交易试图通过跟踪其“所有权”来防止这些资源泄露。当显然MyClass
的实例化成功时,本地资源指针被清除。本地catch
以及析构函数~MyClass
将资源返回到它们的池中(双重释放受上面提到的本地指针清除的保护)。
MyClass的实例化可能会失败并导致两个步骤(1)实际内存分配异常,或(2)构造函数体本身。我对#1没有问题,但在#2的情况下,如果在m_resA
之后抛出异常并且m_resB
已经确定。导致~MyClass
和myFunc
的清理代码都负责将这些资源返回到池中。
这是一个合理的问题吗?
我考虑过的选项,但不喜欢:
bad_alloc
MyClass没有获得所有权。这将需要构造函数中的try-catch以确保ABC() ...more code here...
中的任何分配失败不会与分配MyClass的失败混淆。我忽略了一个干净,简单的解决方案吗?
class SomeExtResourceA;
class SomeExtResourceB;
class MyClass {
private:
// These resources come out of a resource pool not allocated with "new" for each use by MyClass
SomeResourceA* m_resA;
SomeResourceB* m_resB;
public:
MyClass(SomeResourceA* resA, SomeResourceB* resB):
m_resA(resA), m_resB(resB)
{
ABC(); // ... more code here, could throw exceptions
}
~MyClass(){
if(m_resA){
m_resA->Release();
}
if(m_resB){
m_resB->Release();
}
}
};
void myFunc(void)
{
SomeResourceA* resA = NULL;
SomeResourceB* resB = NULL;
MyClass* pMyInst = NULL;
try {
resA = g_pPoolA->Allocate();
resB = g_pPoolB->Allocate();
pMyInst = new MyClass(resA,resB);
resA=NULL; // ''ownership succesfully transfered to pMyInst
resB=NULL; // ''ownership succesfully transfered to pMyInst
// Do some work with pMyInst;
...;
delete pMyInst;
} catch (...) {
// cleanup
// need to check if resA, or resB were allocated prior
// to construction of pMyInst.
if(resA) resA->Release();
if(resB) resB->Release();
delete pMyInst;
throw; // rethrow caught exception
}
}
答案 0 :(得分:3)
以下是您发布双重呼叫的机会:
void func()
{
MyClass a(resourceA, resourceB);
MyClass b(a);
}
糟糕。
如果您使用RIAA包装资源,那么您将不太可能犯错误。这样做很容易出错。您目前在MyClass上缺少复制构造函数和赋值运算符,这可能会导致对Release()的双重调用,如上所示。
由于处理资源的复杂性,类应该只拥有一个资源。如果您有多个资源将其所有权委托给专门用于其所有权的类,并在您的类中使用多个这些对象。
但是我们做了一些假设:
共享和计算资源。使用Acquire()递增计数,并使用Release()递减计数。当计数达到零时,它们会自动销毁。
class ReferenceRapper
{
ReferenceBase* ref;
public:
ReferenceWrapper(ReferenceBase* r) : ref (r) {/* Pool set the initial count to 1 */ }
~ReferenceWrapper() { if (ref) { ref->Release();} }
/*
* Copy constructor provides strong exception guarantee (aka transactional guarantee)
* Either the copy works or both objects remain unchanged.
*
* As the assignment operator is implemented using copy/swap it also provides
* the strong exception guarantee.
*/
ReferenceWrapper(ReferenceWrapper& copy)
{
if (copy.ref) {copy.ref->Acquire();}
try
{
if (ref) {ref->Release();}
}
catch(...)
{
if (copy.ref)
{ copy.ref->Release(); // old->Release() threw an exception.
// Must reset copy back to its original state.
}
throw;
}
ref = copy.ref;
}
/*
* Note using the copy and swap idium.
* Note: To enable NRVO optimization we pass by value to make a copy of the RHS.
* rather than doing a manual copy inside the method.
*/
ReferenceWrapper& operator(ReferenceWrapper rhsCopy)
{
this->swap(rhsCopy);
}
void swap(ReferenceWrapper& rhs) throws ()
{
std::swap(ref, rhs.ref);
}
// Add appropriate access methods like operator->()
};
现在已经完成了艰苦的工作(管理资源)。真正的代码变得微不足道。
class MyClass
{
ReferenceWrapper<SomeResourceA> m_resA;
ReferenceWrapper<SomeResourceB> m_resB;
public:
MyClass(ReferenceWrapper<SomeResourceA>& a, ReferenceWrapper<SomeResourceB>& b)
: m_resA(a)
, m_resB(b)
{
ABC();
}
};
void myFunc(void)
{
ReferenceWrapper<SomeResourceA> resA(g_pPoolA->Allocate());
ReferenceWrapper<SomeResourceB> resB(g_pPoolB->Allocate());
std::auto_ptr<MyClass> pMyInst = new MyClass(resA, resB);
// Do some work with pMyInst;
}
如果我们假设一个资源只有一个所有者而且没有共享,那么它就变得微不足道了:
代码:
class MyClass
{
std::auto_ptr<SomeResourceA> m_resA;
std::auto_ptr<SomeResourceB> m_resB;
public:
MyClass(std::auto_ptr<SomeResourceA>& a, std::auto_ptr<SomeResourceB>& b)
: m_resA(a)
, m_resB(b)
{
ABC();
}
};
void myFunc(void)
{
std::auto_ptr<SomeResourceA> resA(g_pPoolA->Allocate());
std::auto_ptr<SomeResourceB> resB(g_pPoolB->Allocate());
std::auto_ptr<MyClass> pMyInst = new MyClass(resA, resB);
// Do some work with pMyInst;
}
答案 1 :(得分:1)
我在这个小代码中没有看到任何泄漏。
如果构造函数抛出异常,则不会调用析构函数,因为该对象从未存在过。因此,我也看不到双重删除!
来自Herb Sutter的这篇文章:Constructor Exceptions in C++, C#, and Java:
我认为这应该清除你的怀疑!
答案 2 :(得分:1)
你的代码很好。但为了使它更好,请使用某种智能指针!
编辑:例如,您可以使用shared_ptr:
class SomeExtResourceA;
class SomeExtResourceB;
class MyClass {
private:
// These resources come out of a resource pool not allocated with "new" for each use by MyClass
shared_ptr<SomeResourceA> m_resA;
shared_ptr<SomeResourceB> m_resB;
public:
MyClass(const shared_ptr<SomeResourceA> &resA, const shared_ptr<SomeResourceB> &resB):
m_resA(resA), m_resB(resB)
{
ABC(); // ... more code here, could throw exceptions
}
}
};
void myFunc(void)
{
shared_ptr<SomeResourceA> resA(g_pPoolA->Allocate(), bind(&SomeResourceA::Release, _1));
shared_ptr<SomeResourceB> resB(g_pPoolB->Allocate(), bind(&SomeResourceB::Release, _1));
MyClass pMyInst(resA,resB);
// you can reset them here if you want, but it's not necessery:
resA.reset(), resB.reset();
// use pMyInst
}
我发现RAII的这个解决方案要简单得多。
答案 3 :(得分:0)
只需将if (pMyInst) { ... }
放在发布/删除代码中,您就可以了。
答案 4 :(得分:0)
明确取得所有权的经典用法是std :: auto_ptr
这样的事情:
std::auto_ptr<SomeResourceA>(g_pPoolA->Allocate()) resA;
std::auto_ptr<SomeResourceB>(g_pPoolB->Allocate()) resB;
pMyInst = new MyClass(resA.release(),resB.release());
您在调用构造函数时转移所有权。