这里是否存在资源泄漏/双重免费的可能性?

时间:2011-03-20 19:06:30

标签: c++ memory-leaks

以下示例(未编译,因此我不会保证语法)从资源池中提取两个资源(未使用new分配),然后在特定事务的持续时间内将它们与MyClass“绑定”在一起

此处由myFunc实施的交易试图通过跟踪其“所有权”来防止这些资源泄露。当显然MyClass的实例化成功时,本地资源指针被清除。本地catch以及析构函数~MyClass将资源返回到它们的池中(双重释放受上面提到的本地指针清除的保护)。

MyClass的实例化可能会失败并导致两个步骤(1)实际内存分配异常,或(2)构造函数体本身。我对#1没有问题,但在#2的情况下,如果在m_resA之后抛出异常并且m_resB已经确定。导致~MyClassmyFunc的清理代码都负责将这些资源返回到池中。

这是一个合理的问题吗?

我考虑过的选项,但不喜欢:

  • 智能指针(如boost的shared_ptr)。我没有看到如何应用于资源池(除了包装在另一个实例中)。
  • 允许在此级别发生双重释放,但在资源池中进行保护。
  • 尝试使用异常类型 - 尝试推断如果发现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
  }
}

5 个答案:

答案 0 :(得分:3)

以下是您发布双重呼叫的机会:

void func()
{
   MyClass   a(resourceA, resourceB);
   MyClass   b(a);
}

糟糕。

如果您使用RIAA包装资源,那么您将不太可能犯错误。这样做很容易出错。您目前在MyClass上缺少复制构造函数和赋值运算符,这可能会导致对Release()的双重调用,如上所示。

由于处理资源的复杂性,类应该只拥有一个资源。如果您有多个资源将其所有权委托给专门用于其所有权的类,并在您的类中使用多个这些对象。

编辑1

但是我们做了一些假设:

共享和计算资源。使用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;
}

编辑2根据下面的评论,资源只有一个所有者:

如果我们假设一个资源只有一个所有者而且没有共享,那么它就变得微不足道了:

  1. 删除Release()方法并在析构函数中完成所有工作。
  2. 更改Pool方法,以便将指针构造为std :: auto_ptr并返回std :: auto_ptr。
  3. 代码:

    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

  • 构造函数在概念上变成了一个 适当大小的原始记忆 进入一个服从它的物体 不变量。 对象的生命周期 直到它的构造函数才开始 成功完成。如果一个 构造函数以抛出结尾 例外,这意味着它永远不会 完成创建对象和 设置其不变量 - 和 at 特殊构造函数 退出,对象不仅没有 存在,但从未存在过。
  • 概念上的析构函数/处理程序 将对象转回原始内存。 因此,就像其他所有 非私人方法, 析构函数/处置程序假设为 “this”对象的前提条件 实际上是一个有效的对象,它的 不变量持有。因此, 析构函数/处理程序只能运行 成功构建了对象。

我认为这应该清除你的怀疑!

答案 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());

您在调用构造函数时转移所有权。