带有引用计数器的c++结构导致内存泄漏
C++ struct with reference counter causes memory leak
我正试图实现我自己的结构与引用计数器,是在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次。
那么我的内存泄漏是从哪里来的?
EDIT:这是根据下面的建议更新的版本。它仍然产生相同的结果:
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("newn");
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("deleten");
_sf.pbStopFlag = NULL;
}
}
}
private:
mutable _S_FLAG _sf;
};
引用计数需要在对象的逻辑引用之间共享。
相反,你正在复制它。
这并不能完全解释你观察到的效果,但它是更基本的:你担心引擎发出一些不好的声音,我是指出这辆车有方形车轮,所以不要担心引擎。
例如:
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<>
和类似的东西。此外,并非所有的"共享"指针都是相似的。回顾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 exitingn";
// 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 exitedn";
// 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
很有用。
- 从构造函数抛出异常时如何克服内存泄漏
- malloc() 可能出现内存泄漏
- 这个极客对极客的trie实现是否存在内存泄漏问题
- 尽管遵循了规则,内存泄漏在哪里
- 为什么调用堆栈数组会导致内存泄漏
- 在简单示例中,Python3 + ctypes 回调会导致内存泄漏
- 使用模板类的自定义列表类型中的内存泄漏
- 为什么以下C++代码中存在内存泄漏?
- OpenCV 我应该使用智能指针来防止内存泄漏吗?
- 我是否生成线程并导致内存泄漏?
- 多线程程序中出现意外的内存泄漏
- 为什么此函数会导致内存泄漏?
- 在 C++ 库中使用cythonized python时内存泄漏
- 需要帮助查找内存泄漏
- 瓦尔格林德的内存泄漏使用新的
- 无法找出我的代码中的内存泄漏
- C++ 结构内部的unordered_map会导致内存泄漏问题吗?
- 可视化 使用 VS Code 查找C++应用程序中的内存泄漏
- Shared_ptr双链接列表内存泄漏
- C++ 在类中使用常量引用文本时 O2 内存泄漏