解决将对象传递给抛出的函数时可能出现的内存泄漏问题

Solve possible memory leak when passing an object to a function that throws

本文关键字:内存 问题 泄漏 函数 解决 对象      更新时间:2023-10-16

考虑函数foo(MyClass*mc)应该将mc的副本保留到内部数据结构中的情况,并保证该对象在不再使用时会被删除。

void foo(MyClass* mc) // acquires ownership of mc; may throw
{
// code that may throw
bar(mc); // acquires mc; may also throw
}

当此函数执行可能引发的代码(例如,OutOfMemory异常)时,就会出现问题。如果在指针保存到数据结构之前引发异常,那么显然应该在函数展开之前释放对象,因为调用者不再对此负责(调用者甚至不知道指针是否真的保存在数据结构中)。

可以使用RAII通过范围保护来处理这一问题,但它看起来非常笨拙,并且会产生一些开销(必须在每个获取指针的函数中进行)。

每次获取动态分配的对象时,真的需要这样做吗?或者有更整洁的方法吗?!

template <class T>
struct VerifyAcq {
T* ptr;
bool done;
VerifyAcq(T* ptr):ptr(ptr) { done = false; }
~VerifyAcq() {
if (!done) delete ptr;
}
};
void foo(MyClass* mc) // acquires mc; may throw
{
VerifyAcq<MyClass> va(mc);
// code that may throw
bar(mc); // acquires mc; may throw; must implement the same mechanism!
va.done = true;
}
// Note: there might be no public way of "undoing" what bar has done (no rollbak)
// and even if there was, what if it could also throw?...

调用程序无法捕获异常以删除指针,因为在引发异常之前,函数可能已成功将指针添加到数据结构中,释放对象会使数据结构不健全(悬空指针)。

当我开始阅读代码时,我在这一点上停止了:

void foo(MyClass* mc) // acquires ownership of mc; may throw

发表评论并不是记录所有权收购的最佳方式。最好的方法是使用类型系统。

void foo(std::unique_ptr<MyClass> mc) // no comment required

修复接口也最终解决了问题。

(如果您的标准库中没有unique_ptr,那么还有其他选择,例如Howard Hinnant在C++03中的模拟)。

在提到RAII时,您的想法是正确的。

在您提到的场景中,您指出只有当bar函数以某种方式未能存储指针时,才应该进行删除。像boost::shared_ptr这样的共享指针非常适合:

void foo(boost::shared_ptr<MyClass> mc)
{
// code that may throw
bar(mc); // acquires mc; may also throw
}
void bar(boost::shared_ptr<MyClass> mc)
{
// code that may store the shared_ptr somewhere
}

只要资源(指针)至少有一个活动的共享指针,它就不会被删除。一旦最后一个共享指针被销毁或重置,资源就会被删除。

例如:如果bar函数存储共享指针,那么当foo函数结束时不会发生删除。或者,如果bar函数未能存储共享指针,则在foo结束时将发生删除(假设没有其他共享指针对资源有效)。