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

C++ 这里是否存在资源泄漏/双重免费的可能性?,c++,memory-leaks,C++,Memory Leaks,下面的示例(未编译,因此我不担保语法)从资源池中提取两个资源(未使用new分配),然后在特定事务期间将它们与MyClass“绑定”在一起 该事务在这里由myFunc实现,它试图通过跟踪这些资源的“所有权”来防止这些资源泄漏。当MyClass的实例化明显成功时,本地资源指针被清除。本地catch,以及析构函数~MyClass将资源返回到它们的池中(通过上述清除本地指针来保护双空闲) MyClass的实例化可能会失败,并在两个步骤中导致异常(1)实际内存分配,或(2)构造函数主体本身。我对#1没有问

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

该事务在这里由
myFunc
实现,它试图通过跟踪这些资源的“所有权”来防止这些资源泄漏。当
MyClass
的实例化明显成功时,本地资源指针被清除。本地
catch
,以及析构函数
~MyClass
将资源返回到它们的池中(通过上述清除本地指针来保护双空闲)

MyClass的实例化可能会失败,并在两个步骤中导致异常(1)实际内存分配,或(2)构造函数主体本身。我对#1没有问题,但在#2的情况下,如果在设置了
m#resA
&
m#resB
之后引发异常。使
~MyClass
myFunc
的清理代码承担将这些资源返回其池的责任

这是合理的担忧吗

我已经考虑过,但不喜欢:

  • 智能指针(如boost的共享指针)。我没有看到如何应用到资源池(除了在另一个实例中包装)
  • 允许在此级别上发生双自由,但在资源池上进行保护
  • 尝试使用异常类型-尝试推断如果捕获了
    bad\u alloc
    ,MyClass没有获得所有权。这将需要构造函数中的try-catch来确保
    ABC()…此处有更多代码…
    中的任何分配失败不会与分配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
  }
}

我看不出这个小代码有任何漏洞

如果构造函数抛出异常,则不会调用析构函数,因为该对象从不存在。因此,我也没有看到双重删除

赫伯·萨特的这篇文章:

  • 构造函数在概念上转换为 适当大小的原始内存块 变成一个服从其规则的物体 不变量。对象的生命周期 直到它的构造函数才开始 成功完成。如果 构造函数以抛出 例外,这意味着它永远不会 已完成对象的创建,并且 设置其不变量-并在 异常构造函数的点 退出时,对象不仅不会 存在,但从未存在。
  • 概念上的析构函数/处理器 将对象恢复为原始内存。 因此,就像所有其他 非私有方法, 析构函数/处理器假定为 “this”对象为 实际上是一个有效的对象 不变量成立。因此 析构函数/处理器仅在上运行 已成功构建对象。

我想这应该可以消除你的疑虑

只要把
if(pMyInst){…}
放在catch中的release/delete代码上就可以了。

明确拥有所有权的经典用法是std::auto\u ptr

大概是这样的:

std::auto_ptr<SomeResourceA>(g_pPoolA->Allocate()) resA;
std::auto_ptr<SomeResourceB>(g_pPoolB->Allocate()) resB;
pMyInst = new MyClass(resA.release(),resB.release());
std::auto_ptr(g_pPoolA->Allocate())resA;
std::auto_ptr(g_pPoolB->Allocate())resB;
pMyInst=newmyclass(resA.release(),resB.release());

调用构造函数时,您可以转移所有权。

您的代码很好。但为了让它更好,使用某种智能指针

编辑:例如,您可以使用共享\u 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
}
class一些外部资源;
类外部资源B;
类MyClass{
私人:
//这些资源来自一个资源池,MyClass没有为每次使用分配“new”
共享的ptr m_resA;
共享资源;
公众:
MyClass(常数共享\u ptr&resA、常数共享\u ptr&resB):
m_resA(resA),m_resB(resB)
{
ABC();/…此处的更多代码可能引发异常
}
}
};
void myFunc(void)
{
共享资源(g_pPoolA->Allocate(),绑定(&SomeResourceA::Release,_1));
shared_ptr resB(g_pPoolB->Allocate(),bind(&SomeResourceB::Release,_1));
MyClass pMyInst(resA、resB);
//如果需要,您可以在此处重置它们,但这不是必需的:
resA.reset(),resB.reset();
//使用pMyInst
}

我发现使用RAII的解决方案要简单得多。

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

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

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

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

编辑1 Lut us做出一些假设:

资源是共享和计数的。使用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
{
参考包装纸m_resA;
参考包装纸m_resB;
公众:
MyClass(ReferenceWrapper&a、ReferenceWrapper&b)
:m_resA(a)
,m_resB(b)
{
ABC();
}
};
void myFunc(void)
{
ReferenceWrapper resA(g_pPoolA->Allocate());
引用包装器resB(g_pPoolB->Allocate());
std::auto_ptr pMyInst=新的MyClass(resA、resB);
//与pMyInst一起做一些工作;
}
根据以下注释编辑2,即资源只有一个所有者: 如果我们假设一个资源只有一个所有者,并且没有共享,那么它就变得微不足道:

  • 删除Release()方法并执行析构函数中的所有工作
    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;
    }