当前位置:天才代写 > tutorial > C语言/C++ 教程 > C++引用计数的智能指针有效接纳要领

C++引用计数的智能指针有效接纳要领

2017-11-07 08:00 星期二 所属: C语言/C++ 教程 浏览:795

副标题#e#

引用计数指针是否能有效地接纳,对系统意外关机之后数据的规复来说至关重要,要害是要制止工具复制。

奈何从劫难性妨碍中,规复一个恒久运行、系统级的靠山守护历程可能处事,在如今的软件设计进程中,已成为了一个重要的思量因素。当这些软件是由C++语言编成,并利用了引用计数的智能指针时,那么,智能指针的有效接纳,对系统是否具有可伸缩级的规复本领、甚至正确地继承未完成的操纵来说,都显得至关重要。

在本文中,描写了一种要领,可从关机之后的软件规复中,有效地接纳引用计数指针,并且此要领在内存占用方面也很是高效,这种要领的要害在于制止工具复制,而工具复制凡是是由C++中指针引用的串行化与反串行化这种传统技能发生的。当从存档文件中反串行化时,本要领利用了标志(tag)来独一地识别指针工具,且在系统规复时由一个工具缓存来生存指针引用。

本文以一个基于事件的贸易及时功课调治系统来举办演示,其凡是由大型市场咨询公司利用,天天城市在集群事情站上处理惩罚数不胜数的计较任务。

为什么很多C++软件项目会利用自动内存打点技能呢,因为它有以下长处:

² 代码安详性。制止了太早释放一个工具所带来的风险。

² 代码正确性。制止了健忘释放未利用内存所带来的风险。

² 代码模块性。代码中不再需要遮盖着与措施无关的簿记代码。

² 编程简朴性。此刻可假定一种无限内存的计较模式。

² 编程高效性。措施员不再担忧内存打点问题。

引用计数智能指针,有时也称为“计数体术语”,是一种生命期受管的工具,其对引用它的数量,有一个内部的计数器。当内部引用计数为零时,这些工具会自动销毁自身,这是一种很是有用的技能,已运用在很多C++软件产物项目中,因为简朴易行,且无需对语言或编译器举办任何扩展。

引用计数智能指针能进一步界说为一体式或疏散式,一体式智能指针把引用计数放在自身内,而疏散式智能指针则把引用计数放在工具之外。在本文中,利用的是疏散式智能指针方案,这需要在会见实际工具指针之前,在智能指针模板工具中重载 -> 或 * 操纵符,从本质上来说,这也是署理(Proxy)设计模式的一个特例。

就今朝来说,还没有一种方案以高效操作内存的方法描写了奈何规复智能指针,而传统的C++工具串行与反串行化要领,会导致内存低效,因为当一个反串行化的工具碰着一个对它的引用时,老是会建设一个新工具,在最坏的环境下,这会把一个规复后的守护历程的内存耗损量,推到一个无法接管的高度,致使它无法继承运行下去。

问题的引出

传统工具的串行与反串行化方案,也能实现智能指针,只不外在内存上较量低效罢了。在这些传统方案中,当一个工具串行化时,工具内的成员指针被解引用,它的内容与工具一起“串行”进存档文件中。这种要领的问题在于,当反串行化时,成员指针会再次结构,且是每个规复的工具城市这样。

下面以基于事件的功课调治系统来举办讲授,功课界说在CJobDef工具中,其包括了功课的静态属性,如它执行的呼吁、事情目次、及功课执行时的用户ID。而功课界说的运行实例则包装在CJobInst工具中,其包括了一些与实例有关的属性,如它的历程ID、执行参数、及运行汗青记录。在类条理上,每个CJobInst工具都包括了一个成员,其引用到触发这次功课实例的原始CJobDef工具。

图1是软件遏制运行之前的系统,运行时CJobInst工具的多个实例大概会引用至同一个CJobDef工具。在软件遏制及规复后,传统串行化工具规复要领,会导致为每个运行的CJobInst工具,都建设一个CJobDef工具,如图2中所示。

C++引用计数的智能指针有效采取方式

图1:规复之前的工具图

C++引用计数的智能指针有效采取方式

图2:内存低效规复之后的工具图


#p#副标题#e#

这种环境产生在传统的C++类工具中指针成员串行化与反串行化时,例1,是一段带有重载>>与<<操纵符,串行及反串行化CJobInst与CJobDef类指针的CArchive类代码,也证明白这点。

例1:

以下是引用片断:
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;
};

#p#分页标题#e#

在CJobInst中串行化CJobDef的私有成员m_def涉及到挪用CArchive类中适当的<<操纵符,重载的<<操纵符通过把工具属性串行化进一个永久的存档文件,来实现对CJobDef指针的串行化;反串行化CJobDef指针涉及到结构一个新的工具,并挪用>>操纵符从存档文件中更新属性。

办理方案

引用计数智能指针是由担任自CReferable类一个工具实现的,其包括了一个私有引用计数器及用于修改其值的increaseReferenceCount()与decreaseReferenceCount()要领,而相应的Ref模板类,通过->、*、= 操纵符重载,也实现了会见此工具及对生命期的打点。Ref模板对智能指针的赋值操纵,会递增工具的引用计数,而它的析构函数会递减计数。智能指针中的工具只当它的引用计数为零时被销毁。在上面的功课调治系统中,CJobDef工具被包装在一个CJobDefPtr范例中,其由以下语句界说:

以下是引用片断:
typedef Ref<CJobDef> CJobDefPtr;

这个CJobDefPtr范例,正是类CScheduler所用到的范例。当用户提交一个功课到事件功课调治器时,会发生一个CJobDefPtr范例新的工具,且会赋予它CJobDef工具;从此,看成业实例建设时,也正是这个CJobDefPtr范例赋予给了实例。图3演示了类CScheduler利用的CJobDefPtr范例。

C++引用计数的智能指针有效采取方式

图3:功课界说类干系图

#p#副标题#e#

在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;
}
};

#p#副标题#e#

C++引用计数的智能指针有效采取方式

图4:功课工具与CReferableCache全局工具的交互

#p#分页标题#e#

图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:在软件每次重启后的调治系统所用内存巨细

运行任务数 软件重启前的内存占用巨细 软件重启后的内存占用巨细
5000 25M 32M
100000 370M 413M
200000 730M 795M

 

    关键字:

天才代写-代写联系方式