C++垃圾回收 之 智能指针增强方案 (一)

长久以来,C++ 程序员都要花费大量的时间管理指针和内存,指针失效,内存泄漏是 C++ 程序非常常见的 BUG ,而 C++ 语言本身没有比较完善的垃圾回收机制。智能指针可算是一个比较成熟的资源管理方式,但智能指针本身使用引用计数的方式,天生具有不能够检测循环引用这种资源泄漏的问题,虽然 boost 已经提出 weak_ptr 这种解决方案,但 weak_ptr 本身不管理对象的生命周期,仅仅是指针的一个副本,在发现循环依赖时可用来解套,而考虑 week_ptr 方案可能存在的问题:

1、 滥用会造成对象对象生命周期混乱,资源泄漏,就像它的名字一样

2、 只有在发现循环引用时才可用week_ptr 解套

3、 解套本身是一个静态的过程,而对象的依赖关系却是动态的,用静态的方式解决动态的问题则意味着遇到具体问题需要修改代码来接触对象间的循环依赖,不利于代码库的稳定。

当然智能指针本身的问题还有其他几个方面,关于这一点可参考 许式伟 同学的《shared_ptr 四宗罪》  http://blog.csdn.net/xushiweizh/archive/2009/06/24/4295948.aspx

关于流行的垃圾收集算法,网上有很多文章这里不再赘述。

本文所说的智能指针改进,实际上是对引用计数的改进,引用计数本身也有它的优点,即对象真正的生命周期是程序员完全可控制的,即可以很好的利用C++ 的  RAII  ,具体可参见  RAII 和垃圾收集  http://www.wangchao.net.cn/bbsdetail_33487.html

引用计数记录着对象本身当前被其他对象引用的情况,但被引用或引用谁却没有记录( 一般认为没有必要 ) ,这也就是应用计数形成循环引用而无法知道的根本原因 ( 信息不全 ) C# java 等语言本身通过对对象的封装,可在垃圾收集时根据 RTTI ( Runtime Type Information) 来遍历根对象对所有对象的引用,没有引用的对象被视为垃圾。

C++本身不具有 RTTI 机制,所以可以考虑在运行时通过某种机制,记录对象间的引用。

RTTI本身不是为了做垃圾收集用的,它只是提供了一个参考,几 A 对象可能引用了 B 对象,在垃圾收集时可判断 A 对象对 B 对象的引用是否有效,有效则继续往下遍历。

智能指针对象间的依赖关系发生在构造时和赋值时,我们只要控制好这两个时机实现对象间依赖关系的增加和删除,就可解决对象依赖关系信息不全的问题。

具体实现代码如下:

#include  < list >

#include  <windows.h>

#include  <memory>

#include  <assert.h>

#include  < vector >

#include  <stdio.h>

#include  <winbase.h>

typedef std:: list < int >  list_int ;

//typedef LockFreeList<int> list_int;

// 考虑使用 引用计数和 mark-sweep 结合的算法,引用计数可导致立即的内存释放(快速释放开销小)

// mark-sweep在解决循环引用时开销也不小,但考虑能够一次解决所有的循环引用,也算比较值得

// 如果用引用计数来判断循环引用则每 Release 一次,发现非 0 ,都要进行一次依赖判定是否有循环引用

// 无锁链表实现

// 1、把链表拷贝出来 , 并记录原来的表头

// 2、修改复制的链表 

// 3、用 InterlockedCompareExchange, 把表头换掉,发现表头不是原来则返回 1

//(此时原有的表 如果没人读就释放掉,有人读就等读完再释放)

// 实现时要注意释放表的所有权

typedef void (*DESTRUCT_FUNC)( void* );

struct  _REF_INFO

{

_REF_INFO ()

{

nRefCount = 0;

nInnerRefCount = 0;

nAge = 0;

bMark = FALSE;

funcDestruct = NULL;

pData = NULL;

}

int nRefCount; //引用计数

int nInnerRefCount;  //内部引用计数

int nAge; //当前分配的代 , 此区域每被分配一次,就自动 +1

BOOL bMark; //mark-sweep时使用的标记 , 可考虑和 nAge 字段合并

list_int lstDependence; //依赖关系 , 对这个表的修改,怎样实现互斥而又不使用锁?使用 InterlockedExchnage 交换表头,修改完再交换回去,否则等待

DESTRUCT_FUNC funcDestruct; //析构函数指针

void* pData; //实际分配的对象指针

};

typedef struct  _REF_INFO   REF_INFO ,* PREF_INFO ;

typedef std::pair< DESTRUCT_FUNC , void* >  destruct_data ;

typedef std:: vector destruct_data  >  vdestruct_data ;

//线程局部存储索引

unsigned int g_dwThreadHeaderID = 0; //

PREF_INFO  g_pRcInfo = NULL; //管理所有的收集对象

int   g_nRcInfoSize = 0; //收集对象数组大小

int   g_nRcUsedIndex = 0; //当前使用的收集对象数组位置,小于 Size 时直接从末尾分配

unsigned int* g_pFreeIndex = NULL; //当前空闲的索引

int   g_nFreeRead = 0;

int   g_nFreeWrite = 0;

unsigned int g_uFreeMask = 0;

// 使用线程局部存储

__declspec( thread ) int tls_i_cur_parent_index = 0;

inline int GetCurParentIndex()

{

return tls_i_cur_parent_index;

}

inline void SetCurParentIndex( int nIndex )

{

tls_i_cur_parent_index = nIndex;

}

int AllocRefIndex() //分配一个空闲的索引

{

int nRet = 0;

if( g_nFreeRead != g_nFreeWrite ) //从空闲队列分配 , 分配完增加引用的代

{

nRet = g_pFreeIndex[g_nFreeRead];

g_nFreeRead ++;

g_nFreeRead &= g_uFreeMask;

g_pRcInfo[nRet].lstDependence.clear(); //清空依赖关系表

}

else if( g_nRcUsedIndex < g_nRcInfoSize )

{

nRet = g_nRcUsedIndex;

g_nRcUsedIndex ++;

}

else //增大分配表 , 再分配 会出现  g_nRcUseIndex < g_nRcInfoSize  的情况 , 分配失败要抛出异常

{

PREF_INFO  pNew = NULL;

unsigned int* pNewFreeIndex = NULL;

if( g_nRcInfoSize != 0 )

{

g_nRcInfoSize += g_nRcInfoSize;

pNew = new  REF_INFO  [g_nRcInfoSize];

pNewFreeIndex = new unsigned int [g_nRcInfoSize];

int i;

for( i=0;i<g_nRcUsedIndex;i++ )

{

pNew[i] = g_pRcInfo[i];

}

delete [] g_pRcInfo;

g_pRcInfo = pNew;

delete [] g_pFreeIndex;

g_pFreeIndex = pNewFreeIndex;

}

else

{

g_nRcInfoSize = 1024;

pNew = new  REF_INFO [g_nRcInfoSize]; //g_infoalloc.allocate( g_nRcInfoSize );

pNewFreeIndex = new unsigned int [g_nRcInfoSize]; //new g_byalloc.allocate( g_nRcInfoSize*sizeof(int) );

g_pRcInfo = pNew;

g_pFreeIndex = pNewFreeIndex;

g_nRcUsedIndex = 1; // 0 个永远不用

}

g_uFreeMask = g_nRcInfoSize - 1;

nRet = g_nRcUsedIndex;

g_nRcUsedIndex ++;

}

return nRet;

}

void FreeRefIndex( int nIndex ) //把索引放入空闲队列

{

g_pFreeIndex[g_nFreeWrite] = nIndex;

g_nFreeWrite ++;

g_nFreeWrite &= g_uFreeMask;

}

class  ref_alloc_helper

{

public:

ref_alloc_helper ()

{

m_nAllocedIndex = AllocRefIndex();

g_pRcInfo[m_nAllocedIndex].nRefCount = 1; //分配的时候就分配 1

g_pRcInfo[m_nAllocedIndex].nAge ++;

m_nOldParentIndex = GetCurParentIndex();

SetCurParentIndex( m_nAllocedIndex );

}

~ ref_alloc_helper ()

{

SetCurParentIndex( m_nOldParentIndex );

}

int m_nOldParentIndex; //旧的 ID

int m_nAllocedIndex; //新分配的 ID

};

//考虑用来在 全局 或者堆栈初始化

//BYTE g_dummyData[ sizeof(ref_alloc_helper) ];

//ref_alloc_helper* g_helper = (ref_alloc_helper*)g_dummyData;

template < class  T  > 

class  ogc

{

struct  DestructorTraits

{

static void Destruct(void* pThis)

{

(( T *)pThis)->~ T ();

delete pThis;

}

};

void add_ref( int nNewIndex ,  BOOL  bAddRef = TRUE )

{

m_nMyIndex = nNewIndex;

if( m_nParentIndex != 0 )

{

REF_INFO & rInfo = g_pRcInfo[m_nMyIndex];

if( rInfo.nRefCount > 0 ) //不支持复活

{

rInfo.nRefCount += bAddRef;

REF_INFO & rInfo2 = g_pRcInfo[m_nParentIndex];

rInfo2.lstDependence.push_front( m_nMyIndex ); //

rInfo.nInnerRefCount ++;

}

else

{

m_nMyIndex = 0;

}

}

else

{

REF_INFO & rInfo = g_pRcInfo[m_nMyIndex];

if( rInfo.nRefCount > 0 )

{

rInfo.nRefCount += bAddRef;

}

else

{

m_nMyIndex = 0;

}

}

}

public:

#if  0

//定义不带参数的  CreateObject

static  ogc  CreateObject()

{

int nIndex = AllocRefIndex();

g_pRcInfo[nIndex].nRefCount = 1;

g_pRcInfo[nIndex].nAge ++;

int nOldParentIndex = GetCurParentIndex();

SetCurParentIndex( nIndex );

T * pNew = new  T ();

SetCurParentIndex( nOldParentIndex );

REF_INFO & rInfo = g_pRcInfo[nIndex];

rInfo.funcDestruct =  DestructorTraits ::Destruct;

rInfo.pData = pNew;

ogc < T > ret;

ret.m_nParentIndex = 0; //因为是在栈上分配的,所以没有父对象 ,nOldParentIndex;

ret.m_nMyIndex = nIndex;

return ret;

}

// 定义带参数的  CreateObject

#define  DEFINE_CREATE_OBJECT( _Y_ ) {/

int nIndex = AllocRefIndex();/

int nOldParentIndex = GetCurParentIndex();/

SetCurParentIndex( nIndex );/

T * pNew = new  T ( _Y_ );/

SetCurParentIndex( nOldParentIndex );/

REF_INFO & rInfo = g_pRcInfo[nIndex];/

rInfo.nAge ++;/

rInfo.nRefCount = 1;/

rInfo.funcDestruct =  DestructorTraits ::Destruct;/

rInfo.pData = pNew;/

ogc < T > ret;/

ret.m_nParentIndex = 0;/

ret.m_nMyIndex = nIndex;/

return ret;/

}

#define  PARAM1 x1

template< typename Any1 > 

static  ogc  CreateObject( Any1 x1 )

DEFINE_CREATE_OBJECT( PARAM1 );

#define  PARAMS2 x1,x2

template< typename Any1 , typename Any2  > 

static  ogc  CreateObject( Any1 x1 , Any2 x2 )

DEFINE_CREATE_OBJECT( PARAMS2 );

#define  PARAMS3 x1,x2,x3

template< typename Any1 , typename Any2 , typename Any3 >

static  ogc  CreateObject( Any1 x1 , Any2 x2 , Any3 x3 )

DEFINE_CREATE_OBJECT( PARAMS3 );

#endif

//这里的顺序非常重要!!!,必须先  new ref_alloc_helper  然后再  new T

ogc T * pNew ,  ref_alloc_helper * phelper = new  ref_alloc_helper  )

{

unsigned int dwOffset = (unsigned char*)this - (unsigned char*)&dwOffset;

ref_alloc_helper & helper = *phelper;

REF_INFO & rInfo = g_pRcInfo[helper.m_nAllocedIndex];

rInfo.funcDestruct =  DestructorTraits ::Destruct;

rInfo.pData = pNew;

m_nParentIndex = dwOffset < 4096 ? 0 : helper.m_nOldParentIndex;

add_ref( helper.m_nAllocedIndex , FALSE );

//phelper->~ref_alloc_helper();

delete phelper;

}

ogc ()

{

unsigned int dwOffset = (unsigned char*)this - (unsigned char*)&dwOffset;

//if(dwOffset < 4096) m_nParentIndex = 0; else m_nParentIndex = GetCurParentIndex();

m_nParentIndex = dwOffset < 4096 ? 0 : GetCurParentIndex();

m_nMyIndex = 0;

}

ogc ogc & clsSrc )

{

unsigned int dwOffset = (unsigned char*)this - (unsigned char*)&dwOffset;

m_nParentIndex = dwOffset < 4096 ? 0 : GetCurParentIndex();

add_ref( clsSrc.m_nMyIndex );

}

ogc & operator=( ogc & clsSrc) //增加引用计数 , 如果本身的  m_nParentIndex!=0  也要增加依赖关系

{

reset(); //减少旧的引用计数

add_ref( clsSrc.m_nMyIndex );

return *this;

}

//获得 受管理对象的一个句柄 , 用户可在接口间传递这个句柄

void* GetHandle()

{

return NULL;

}

//用户可根据此句柄,恢复智能指针

ogc & FromHandle( void* pHandle )

{

ogc  ret;

return ret;

}

//判断智能指针是否有效

bool  isValid()

{

return ( m_nMyIndex != 0 );

}

//判断智能指针是否为空

bool  isNull()

{

return ( m_nMyIndex == 0 );

}

// this 指针返回当前对象的智能指针索引

ogc & FromThis(  T * pThis )

{

ogc  ret;

return ret;

}

void reset()

{

if( m_nMyIndex != 0 && g_pRcInfo != NULL )

{

DESTRUCT_FUNC fDestruct = NULL;

void* pData = NULL;

int nAge = 0;

if( m_nParentIndex != 0 )

{

REF_INFO & rInfo = g_pRcInfo[m_nMyIndex];

rInfo.nRefCount --;

if( rInfo.nRefCount == 0 ) //这里没用  <= 0  为的是清理循环引用对象

{

fDestruct = rInfo.funcDestruct;

pData = rInfo.pData;

nAge = rInfo.nAge;

FreeRefIndex( m_nMyIndex );

}

REF_INFO & rInfo2 = g_pRcInfo[m_nParentIndex];

//这里最好考虑 反向搜索 因为新创建的对象一般会比较早被删掉

list_int :: iterator  it;

for( it = rInfo2.lstDependence.begin();

it != rInfo2.lstDependence.end();it++ )

{

if( *it == m_nMyIndex )

{

rInfo2.lstDependence.erase( it );

break;

}

}

rInfo.nInnerRefCount --;

}

else

{

REF_INFO & rInfo = g_pRcInfo[m_nMyIndex];

rInfo.nRefCount --;

if( rInfo.nRefCount == 0 ) //这里没用  <= 0  为的是清理循环引用对象

{

fDestruct = rInfo.funcDestruct;

pData = rInfo.pData;

nAge = rInfo.nAge;

FreeRefIndex( m_nMyIndex );

}

}

//在外部调用析构函数

if( fDestruct != NULL )

{

fDestruct( pData );

//delete this;

printf( "release %d nAge %d/n" ,m_nMyIndex , nAge );

}

m_nMyIndex = 0;

}

}

~ ogc ()

{ //减少引用计数 , 如果本身的  m_nParentIndex!=0  也要减少依赖关系

reset();

}

T * operator->()

{

if( m_nMyIndex != 0 )

{

REF_INFO & rInfo = g_pRcInfo[m_nMyIndex];

return ( T *)rInfo.pData;

}

else

{

throw;

}

}

bool  empty()

{

return (m_nMyIndex == 0);

}

//static global_list; //全局变量指针 , 只有那些  m_nParentIndex=0, 在赋值时, m_nMyIndex 不为 0 时才会记录到这里

private:

int m_nParentIndex;

int m_nMyIndex;

};

//所有的  (nRefCount > nInnerRefCount)  的表示有外部引用,可作为  root  节点 , 通过依赖关系遍历,可达的表示不用被清理

//传递线程参数的时候要注意,一定要使用  handle , 在线程里面转换成受管理对象指针,千万别传  (LPVOID)this;

//至于对象自身怎样获得自己的 handle,( 与 管理的指针比较,获得索引在生成 handle)

// 现在这种实现方式可能在 调用构造函数的时候,堆栈里面的智能指针的父对象指向 正在被创建的索引,但在退出构造函数时,这些都会被释放

// 在构造函数时,由于本对象已分配的 索引是 1 ,不可能有内部指针指向它,具有 root 的特性 , 也就不必去判断当前分配的智能指针到底在哪里 ( 根据自身地址和当前父对象地址比较 )

//是否可考虑写成 迭代方式 , 使用一个  vector  处理需要 Mark

void Mark( int nIndex )

{

assert( g_pRcInfo[nIndex].nRefCount > 0 );

g_pRcInfo[nIndex].bMark = TRUE;

list_int :: iterator  it;

for( it = g_pRcInfo[nIndex].lstDependence.begin();

it != g_pRcInfo[nIndex].lstDependence.end(); it++ )

{

int& nNextIndex = *it;

if( !g_pRcInfo[nNextIndex].bMark )

{

Mark( nNextIndex );

}

}

}

//是否可考虑把 列表拷贝出来  , 然后再做清理工作 , 这样是否能使锁定的时间减少?

static int ogc_collect()

{

int i;

//clear mark

for( i=0;i<g_nRcUsedIndex;i++ )

{

if( g_pRcInfo[i].nRefCount > 0 ) //用  > 0  是因为 删除循环引用时先清 0 ,再删除之后会变 -1

{

g_pRcInfo[i].bMark = FALSE;

}

}

//mark

for( i=0;i<g_nRcUsedIndex;i++ )

{

if( g_pRcInfo[i].nRefCount > 0  //用  > 0  是因为 删除循环引用时先清 0 ,再删除之后会变 -1

&& g_pRcInfo[i].nRefCount > g_pRcInfo[i].nInnerRefCount  //是跟对象 , 因为有外部的指向

&& !g_pRcInfo[i].bMark) //并且没有被标记

{

Mark( i );

}

}

//sweep

vdestruct_data  vDesData;

for( i=0;i<g_nRcUsedIndex;i++ )

{

if( g_pRcInfo[i].nRefCount > 0  //用  > 0  是因为 删除循环引用时先清 0 ,再删除之后会变 -1

&& !g_pRcInfo[i].bMark )

{

REF_INFO & rInfo = g_pRcInfo[i];

rInfo.nRefCount = 0;

rInfo.lstDependence.clear(); //本来就要销毁,且不支持复苏 , 清空了事

vDesData.push_back(  destruct_data ( rInfo.funcDestruct , rInfo.pData ) );

FreeRefIndex( i );

printf( "collect delete %d %d/n" ,i,rInfo.nAge);

}

}

//考虑析构循环引用的对象期间始终保持其有效 , 且考虑从最后一个 Release 的对象,最先析构 ( 和其他部分的相关性最大 )

//最后再释放内存

//delete 

vdestruct_data :: iterator  it;

for( it = vDesData.begin();

it != vDesData.end(); it++ )

{

it->first( it->second );

}

return vDesData.size();

}

int ogc_clean()

{

int i;

vdestruct_data  vDesData;

for( i=0;i<g_nRcUsedIndex;i++ )

{

if( g_pRcInfo[i].nRefCount > 0 ) //用  > 0  是因为 删除循环引用时先清 0 ,再删除之后会变 -1

{

REF_INFO & rInfo = g_pRcInfo[i];

rInfo.nRefCount = 0;

rInfo.lstDependence.clear(); //本来就要销毁,且不支持复苏 , 清空了事

vDesData.push_back(  destruct_data ( rInfo.funcDestruct , rInfo.pData ) );

FreeRefIndex( i );

printf( "clean delete %d/n" ,i);

}

}

//delete 

vdestruct_data :: iterator  it;

for( it = vDesData.begin();

it != vDesData.end(); it++ )

{

it->first( it->second );

}

delete [] g_pRcInfo;

g_pRcInfo = NULL;

delete [] g_pFreeIndex;

g_pFreeIndex = 0;

g_nRcUsedIndex = 0;

g_nRcInfoSize = 0;

g_nRcInfoSize = 0;

g_nFreeWrite = 0;

return vDesData.size();

}

class  A ;

class  B

{

public:

B (){

};

~ B (){

};

ogc < A > m_pA;

};

class  A {

public:

A ():m_pB(new  B )

{

}

A (int x,int y):m_pB(new  B )

{

int nCount = 0;

{

nCount = ogc_collect();

m_pB->m_pA =  ogc < A >(); //(new B);

nCount = ogc_collect();

//确实,在这里初始化的确实 , 父对象指向了 A, 不过不会被收集,且退出时会销毁

ogc < B > pB(new  B ); //现在上面说的情况已经不存在了

nCount = ogc_collect();

}

nCount = ogc_collect();

};

~ A (){

};

public:

ogc < B > m_pB;

};

class  C :public  B ,public  A

{

public:

C ()

{

}

~ C ()

{

}

private:

ogc < B > m_pX;

};

int main(int argc, char* argv[])

{

int nSize = sizeof( REF_INFO );

nSize = sizeof( LockFreeList <int>);

int nCount = 0;

printf( "%08x/n" ,&nCount);

{

//ogc<A> pA = ogc<A>::CreateObject( 1,2 );

//ogc<B> pB = ogc<B>::CreateObject();

ogc < A > pA =  ogc < A >( new  A (1,2) );

ogc < B > pB =  ogc < B >( new  B  );

ogc < C > pC =  ogc < C >( new  C  );

//ogc<B> pB( new B );

pB->m_pA = pA;

pA->m_pB = pB;

nCount = ogc_collect();

}

nCount = ogc_collect();

nCount = ogc_collect();

ogc_clean();

return 0;

}

说明:

本垃圾收集算法的基本思想是利用引用计数维护对象的基本生命周期,用mark-sweep 算法来解决对象间循环引用造成的内存泄漏。

根对象的判断是通过对象的  nRefCount  和 nInnerRefCount 来统计的,内部引用计数是被所有在堆上分配对象引用的计数。判断一个对象是在对上分配还是栈上分配的方法是通过构造函数时对象地址和当前函数堆栈的差值来判断(当然,不使用这种方式也可以,注释中有部分描述),智能指针创建时的父对象是记录在线程局部存储当中的,在对象构造时,记录这个对象的指针存入线程局部存储,在构造函数时构造的所有智能指针都以此指针为父对象指针,对象构造函数调用完成时,恢复原来的父对象。

使用方法和智能指针一样, ogc < A > pA =  ogc < A >( new  A (1,2) ); 这样得到一个智能指针的实例,使用上也和智能指针一样。

通过Collect() 函数实现垃圾收集,返回收集的对象个数。

以上代码仅仅是示意性代码

改进方向:

1、上述代码仅仅是参考代码,写得还不是很好看,日后慢慢整理

2、本方法可以实现成多线程机制

3、实现多线程机制时可使用无锁数据结构

4、在增加一些内存管理方案,例如  许式伟 的分块垃圾收集,我们这里可以考虑把每个线程的垃圾收集和全局的分开,当然这涉及到整个进程的框架问题。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值