C++引用计数的智能指针有效回收方法
下面,图老师小编带您去了解一下C++引用计数的智能指针有效回收方法,生活就是不断的发现新事物,get新技能~
class CJobDef
{
friend CArchive & operator (CArchive &ar, CJobDef *def)
{
ar def-command;
}
friend CArchive & operator (CArchive &ar, CjobDef *def)
{
ar def-command;
}
private:
std::string command;
};
class CJobInst
{
friend CArchive & operator (CArchive &ar, CJobInst *inst)
{
inst-m_def = new CJobDef;
ar inst-m_def;
}
friend CArchive & operator (CArchive &ar, const CJobInst *inst)
{
ar inst-m_def;
}
private:
CJobDef *m_def;
}; 在CJobInst中串行化CJobDef的私有成员m_def涉及到调用CArchive类中适当的操作符,重载的操作符通过把对象属性串行化进一个永久的存档文件,来实现对CJobDef指针的串行化;反串行化CJobDef指针涉及到构造一个新的对象,并调用操作符从存档文件中更新属性。 解决方案 引用计数智能指针是由继续自CReferable类一个对象实现的,其包含了一个私有引用计数器及用于修改其值的increaseReferenceCount()与decreaseReferenceCount()方法,而相应的Ref模板类,通过-、*、= 操作符重载,也实现了访问此对象及对生命期的治理。Ref模板对智能指针的赋值操作,会递增对象的引用计数,而它的析构函数会递减计数。智能指针中的对象只当它的引用计数为零时被销毁。在上面的作业调度系统中,CJobDef对象被包装在一个CJobDefPtr类型中,其由以下语句定义:以下是引用片段:
typedef RefCJobDef CJobDefPtr; 这个CJobDefPtr类型,正是类CScheduler所用到的类型。当用户提交一个作业到事件作业调度器时,会产生一个CJobDefPtr类型新的对象,且会赋予它CJobDef对象;此后,当作业实例创建时,也正是这个CJobDefPtr类型赋予给了实例。图3演示了类CScheduler使用的CJobDefPtr类型。 图3:作业定义类关系图 在CJobDefPtr类中,赋值=操作符递增了CJobDef对象CReferable基类中的引用计数,而delete操作符递减了这个引用计数。包装在CJobDefPtr对象中的CJobDef对象不会被销毁,直到它的引用计数为零,这也说明了在系统中,没有其他任何对象引用CJobDef对象,它可以安全地被销毁了。 再次提醒,从作业中创建的作业实例,被包装在一个CJobInst类中。与CJobDef一样,类CScheduler只知道它对应版本的智能指针CJobInstPtr,而此对象的实例也会一直保持到没有对它的引用为止。 另外,在系统中,还包括了另外三个特性,以便使调度系统可高效地恢复: ² 类CReferable增加了一个tag属性,以唯一地识别每个创建的指针实例,同时有一个getTag()方法可用于访问此属性。 ² Ref模板类在称为CReferableCache的全局对象缓存中治理它的对象,此全局对象缓存可由其他智能指针对象访问。 ² Ref模板类添加了一个impersonate()方法,其答应一个智能指针以给定的tag转换为另一个智能指针。 当一个新的CJobDefPtr或CJobInstPtr被创建时,在CReferable基类构造函数中,会分配给对象唯一的一个tag。这个tag可由几种方式产生,但任一种方式都必须保证在每次软件运行时,都会有一个唯一的ID。一个简单的方案是使用一个静态、全局的计数器对象,其在存档文件中存储了上一次产生的ID,由此可保证甚至在有多个软件实例运行的条件下,都能单调不重复地递增此ID。 分配给智能指针的tag,唯一地标识出一个指针,而把此tag存入一个存档文件就是对象串行化过程的责任了。对象的串行化过程,可通过CReferable基类的getTag()方法,来访问此tag,接下来,对象的反串行化过程使用此tag,在软件恢复时,来重建正确的对象指针实例引用。下面是反串行化过程必须执行的步骤: ² 从存档文件中恢复tag。 ² 从tag标识的存档文件中,恢复对象属性。 ² 以此tag为界调用impersonate()方法,恢复正确的指针对象的引用。 Impersonate()会对是否一个tag索引了在全局CReferableCache对象集中的一个对象进行检查,假如未找到此tag相应的对象,那么此对象会添加到CReferableCache中,并用此tag作为它的索引值。然而,假如一个对象已经存在于全局CReferableCache对象集中,通过以新引用来调用set()方法,你可以舍弃老引用,且无关的对象复制操作也会自动被删除。例2使用了这种技术来实现智能指针。 例2:以下是引用片段:
class CJobDef : public CReferable
{
friend CArchive &operator (CArchive &ar, const CJobDefPtr &cand)
{
ar cand-getTag();
CArchive ar_def(cand-getTag(), CArchive::WRITE);
// write object attributes to ar_def
return ar;
}
friend CArchive &operator (CArchive &ar, CJobDefPtr &cand)
{
int tag;
ar tag;
CArchive ar_def(tag, CArchive::READ);
// read object attributes from ar_def
cand.impersonate(tag);
return ar;
}
};
class CJobInst : public CReferable
{
friend CArchive & operator ( CArchive &ar, const CJobInstPtr &cand)
{
ar cand-m_defPtr;
return ar;
}
friend CArchive & operator (CArchive &ar, CJobInstPtr &cand)
{
CJobDefPtr defPtr = new CJobDef;
ar defPtr;
cand-m_defPtr = defPtr;
return ar;
}
}; 图4:作业对象与CReferableCache全局对象的交互 图4描述了系统中类CScheduler、CJobDefPtr、CJobDef、CReferableCache之间的交互,类CReferableCache具有静态成员方法getUniqueTag()、addObject()、deleteObject()。当一个对CJobDef的智能指针创建时,如下:以下是引用片段:
CJobDefPtr jobDefPtr = new CJobDef CScheduler会构造CJobDefPtr和一个CJobDef对象,当对象构造时,会通过CJobDef基类的CReferable构造函数调用getUniqueTag()方法,这就为每个CJobDef对象创建了一个唯一的识别标记(tag)。接下来,CJobDef对象被赋给CJobDefPtr对象,后者会调用它自己的set()方法把CJobDef对象添加进来。 当调用CJobDefPtr赋值操作符函数时,也会调用addObject()方法,假如是第一次赋值的话,它会把CJobDef对象添加进全局CReferableCache;当智能指针被请求替换由tag识别的它内部的对象引用时,impersonate()方法会调用getObject()方法,假如impersonate()方法未找到CReferableCache中标记的对象,那么,CJobDefPtr对象会替换它的内部对象标记,并把它自身添加到CReferableCache缓存集中;最后,当CJobDefPtr被删除及对象的引用计数为零时,deleteObject()方法此时会被调用。 在此所描述的事件调度系统,一般使用在市场咨询数据公司中,其会在网络集群工作站上触发计算任务,当从世界各处的零售商汇集所需信息之后,在每周的三天之中,都会触发计算任务,而这三天中的任意时刻,系统可能也要在集群工作站上运行超过20万个任务。因此,软件在合理内存及CPU消耗的前提下,支持重新启动,就显得非常重要了。表1显示了在系统中运行着多个计算任务时,事件调度守护进程在每次重启后的内存消耗,在系统重启后,较小的内存消耗要归功于软件中使用了上文方法来串行及反串行化不常用的类对象的那些模块。当任务完成时,内存最终将被回收。 表1:在软件每次重启后的调度系统所用内存大小运行任务数软件重启前的内存占用大小软件重启后的内存占用大小500025M32M100000370M413M200000730M795M