带引用计数器的C ++结构导致内存泄漏

时间:2014-07-12 10:09:08

标签: c++ windows visual-studio-2008 memory-leaks struct

我试图使用在Visual Studio 2008的C ++ / Windows编译器下编译的引用计数器来实现我自己的结构。我想出了这个:

struct STOP_FLAG
{
    STOP_FLAG() 
        : mRefCount(0)
        , pbStopFlag(NULL)
    {
        pbStopFlag = new (std::nothrow) volatile BOOL;
        ASSERT(pbStopFlag);
        this->grab();
    }
    ~STOP_FLAG()
    {
        this->release();
    }
    STOP_FLAG(const STOP_FLAG& s) 
        : pbStopFlag(s.pbStopFlag)
        , mRefCount(s.mRefCount)
    {
        //Copy constructor
        this->grab();
    }
    STOP_FLAG& operator = (const STOP_FLAG& s)
    {
        //Assignment operator
        if(pbStopFlag != s.pbStopFlag)
        {
            this->release();
            s.grab();

            pbStopFlag = s.pbStopFlag;
            mRefCount = s.mRefCount;
        }

        return *this;
    }

    //Helper methods
    volatile BOOL* operator->() const {return pbStopFlag;}      //x->member
    volatile BOOL& operator*() const {return *pbStopFlag;}      //*x, (*x).member
    operator volatile BOOL*() const {return pbStopFlag;}        //T* y = x;
    operator bool() const {return pbStopFlag != NULL;}          //if(x)

private:
    void grab() const 
    {
        //++mRefCount;
        ::InterlockedIncrement(&mRefCount);
    }

    void release() const
    {
        ASSERT(mRefCount > 0);
        //--mRefCount;
        LONG mCnt = ::InterlockedDecrement(&mRefCount);

        if(mCnt == 0)
        {
            ASSERT(pbStopFlag);
            if(pbStopFlag)
            {
                delete pbStopFlag;
                pbStopFlag = NULL;
            }
        }
    }
private:
    mutable volatile BOOL* pbStopFlag;
    mutable LONG mRefCount;
};

但是当我使用调试器测试以下内容(在单个线程中运行)时:

{
    STOP_FLAG sf;

    {
        STOP_FLAG s2(sf);

        s2 = sf;

        STOP_FLAG s3;

        s3 = s2;

        STOP_FLAG s4[3];
        s4[0] = s3;
        s4[1] = s3;
        s4[2] = s3;

        STOP_FLAG s5;
        s3 = s5;
    }
}

我的new volatile BOOL运算符恰好被调用了6次而delete只被调用了5次。

那么我的内存泄漏来自哪里?

编辑:在下面的建议之后,这是一个更新版本。它仍然会产生相同的结果:

struct _S_FLAG{
    volatile BOOL* pbStopFlag;
    LONG mRefCount;

    _S_FLAG(volatile BOOL* pb, LONG cntr)
    {
        pbStopFlag = pb;
        mRefCount = cntr;
    }
};

struct STOP_FLAG
{
    STOP_FLAG() 
        : _sf(NULL, 0)
    {
        _sf.pbStopFlag = new (std::nothrow) volatile BOOL;
        TRACE("new\n");
        ASSERT(_sf.pbStopFlag);
        this->grab();
    }
    ~STOP_FLAG()
    {
        this->release();
    }
    STOP_FLAG(const STOP_FLAG& s) 
        : _sf(s._sf.pbStopFlag, s._sf.mRefCount)
    {
        //Copy constructor
        this->grab();
    }
    STOP_FLAG& operator = (const STOP_FLAG& s)
    {
        //Assignment operator
        if(_sf.pbStopFlag != s._sf.pbStopFlag)
        {
            this->release();
            s.grab();

            _sf.pbStopFlag = s._sf.pbStopFlag;
            _sf.mRefCount = s._sf.mRefCount;
        }

        return *this;
    }

    //Helper methods
    volatile BOOL* operator->() const {return _sf.pbStopFlag;}      //x->member
    volatile BOOL& operator*() const {return *_sf.pbStopFlag;}      //*x, (*x).member
    operator volatile BOOL*() const {return _sf.pbStopFlag;}        //T* y = x;
    operator bool() const {return _sf.pbStopFlag != NULL;}          //if(x)

private:
    void grab() const 
    {
        //++mRefCount;
        ::InterlockedIncrement(&_sf.mRefCount);
    }

    void release() const
    {
        ASSERT(_sf.mRefCount > 0);
        //--mRefCount;
        LONG mCnt = ::InterlockedDecrement(&_sf.mRefCount);

        if(mCnt == 0)
        {
            ASSERT(_sf.pbStopFlag);
            if(_sf.pbStopFlag)
            {
                delete _sf.pbStopFlag;
                TRACE("delete\n");
                _sf.pbStopFlag = NULL;
            }
        }
    }
private:
    mutable _S_FLAG _sf;
};

1 个答案:

答案 0 :(得分:4)

引用计数需要在对象的逻辑引用之间共享。

相反,你要复制它。

这并没有准确地解释你观察到的效果,但它更为基础:你担心引擎发出的一些声音,我指出这辆车有正方形轮子所以不要担心引擎。

例如:

STOP_FLAG s4[3];
s4[0] = s3;
s4[1] = s3;
s4[2] = s3;

这些中的每一个都正确地破坏了s4[]的每个元素中的先前共享块(它实际上并未与任何元素共享,因为每个元素都是单个实例,并且从不在任何地方分配/复制)。但请考虑一下这段代码的作用:

//Assignment operator
if(pbStopFlag != s.pbStopFlag)
{
    this->release();
    s.grab();

    pbStopFlag = s.pbStopFlag;
    mRefCount = s.mRefCount; // <<== makes a one-time copy of the ref count
}

请注意添加的评论。因为引用计数与对象而不是共享数据相关联,所以每个对象跟踪的mRefCount完全没用。完成此操作后,s[0]s[2]都指向相同的共享数据,但每个都有不同的引用计数。这样,s内的引用计数的变化不会反映在先前的赋值或构造中连接到同一共享数据块的每个对象。

这些是std::shared_ptr<>之类的东西。此外,并非所有&#34;共享&#34;指针是相似的。检查std::make_shared<>std::shared_ptr<>的实际构造的差异是否值得进行一些研究,但只需说其中一个与您在此处尝试的类似,另一个管理单个通过通常称为压缩对的分配。

您的代码的修改版本显然是线程安全的,但详细信息如下所示,您可以通过以下方式完成本答案中解释的内容。它绝不是一些银弹,显然需要为线程安全交换等工作,但它记录了如何在单个共享块中共享引用计数数据来修复上述问题:


<强> Live Code Here

#include <iostream>
#include <algorithm>
#include <utility>

typedef long LONG;
typedef int BOOL;

struct STOP_FLAG
{
    STOP_FLAG()
        : shared(new Shared())
    {
    }

    ~STOP_FLAG()
    {
        shared->release();
    }

    // though not mandatory, note the use of the comma-operator to establish
    // the source object's shared data grab before constructing the new object's
    // shared member pointer. By the time the constructor body is entered the
    // grab is complete and the shared member is properly setup.
    STOP_FLAG(const STOP_FLAG& s)
        : shared((s.shared->grab(), s.shared))
    {
    }

    // assignment operator by copy/swap idiom
    STOP_FLAG& operator = (STOP_FLAG s)
    {
        std::swap(shared, s.shared);
        return *this;
    }

private:
    struct Shared
    {
        BOOL value;
        LONG refCount;

        Shared() : value(), refCount()
        {
            grab();
        }

        void grab()
        {
            std::cout << '\t' << __PRETTY_FUNCTION__ << ':' << refCount+1 << '\n';
            ++refCount;
        }

        void release()
        {
            std::cout << '\t' << __PRETTY_FUNCTION__ << ':' << refCount-1 << '\n';
            if (!--refCount)
                delete this;
        }

    } *shared;
};

int main()
{
    std::cout << "STOP_FLAG sf;" << '\n';
    STOP_FLAG sf; // one block
    {
        std::cout << "STOP_FLAG s2(sf);" << '\n';
        STOP_FLAG s2(sf);  // two objets, one shared block, refcount=2

        // still two objects, after settling, one shared block, refcount=2
        std::cout << "s2 = sf;" << '\n';
        s2 = sf; 

        // three objects, two shared (refcount=2), one stand-alone (refcount=1)
        std::cout << "STOP_FLAG s3;" << '\n';
        STOP_FLAG s3; 

        // three objects, one shared block, (refcount=3)
        std::cout << "s3 = s2;" << '\n';
        s3 = s2; 

        // three more objects, all singular, 
        std::cout << "STOP_FLAG s4[3];" << '\n';
        STOP_FLAG s4[3];

        // each assignment below attaches to sf, detaches prior content.
        std::cout << "s4[0] = s3;" << '\n';
        s4[0] = s3;
        std::cout << "s4[1] = s3;" << '\n';
        s4[1] = s3;
        std::cout << "s4[2] = s3;" << '\n';
        s4[2] = s3;

        // by this point every object so far all shared the same
        //  shared data, refcount=6.

        // another stand-alone object
        std::cout << "STOP_FLAG s5;" << '\n';
        STOP_FLAG s5;

        // s3 attaches to s5, detaches from prior shared data
        //  meaning we now have two shared data blocks with
        // reference counts of 2 and 5 respectively.
        std::cout << "s3 = s5;" << '\n';
        s3 = s5;

        std::cout << "scope exiting\n";

        // in leaving scope, *all* objects are detached except ONE
        //  (the one outside of this; sf), which leaves a single 
        // shared block with refcount of 1 only.
    }
    std::cout << "scope exited\n";

    // the last object, sf, is released here.
}

<强>输出

STOP_FLAG sf;
    void STOP_FLAG::Shared::grab():1
STOP_FLAG s2(sf);
    void STOP_FLAG::Shared::grab():2
s2 = sf;
    void STOP_FLAG::Shared::grab():3
    void STOP_FLAG::Shared::release():2
STOP_FLAG s3;
    void STOP_FLAG::Shared::grab():1
s3 = s2;
    void STOP_FLAG::Shared::grab():3
    void STOP_FLAG::Shared::release():0
STOP_FLAG s4[3];
    void STOP_FLAG::Shared::grab():1
    void STOP_FLAG::Shared::grab():1
    void STOP_FLAG::Shared::grab():1
s4[0] = s3;
    void STOP_FLAG::Shared::grab():4
    void STOP_FLAG::Shared::release():0
s4[1] = s3;
    void STOP_FLAG::Shared::grab():5
    void STOP_FLAG::Shared::release():0
s4[2] = s3;
    void STOP_FLAG::Shared::grab():6
    void STOP_FLAG::Shared::release():0
STOP_FLAG s5;
    void STOP_FLAG::Shared::grab():1
s3 = s5;
    void STOP_FLAG::Shared::grab():2
    void STOP_FLAG::Shared::release():5
scope exiting
    void STOP_FLAG::Shared::release():1
    void STOP_FLAG::Shared::release():4
    void STOP_FLAG::Shared::release():3
    void STOP_FLAG::Shared::release():2
    void STOP_FLAG::Shared::release():0
    void STOP_FLAG::Shared::release():1
scope exited
    void STOP_FLAG::Shared::release():0

您可能会发现boost::intrusive_ptr有用。