C++智能指针.docx
- 文档编号:9402068
- 上传时间:2023-02-04
- 格式:DOCX
- 页数:25
- 大小:26.08KB
C++智能指针.docx
《C++智能指针.docx》由会员分享,可在线阅读,更多相关《C++智能指针.docx(25页珍藏版)》请在冰豆网上搜索。
C++智能指针
【C++】智能指针类和OpenCV的Ptr模板类
2015-03-2921:
18
智能指针类
引用计数
智能指针(smartpointer)的一种通用实现技术是使用引用计数(referencecount)。
智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。
引用计数为0时,删除对象。
其基本使用规则是:
每次创建类的新对象时,初始化指针并将引用计数置为1。
当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相应的引用计数的值。
对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数的值(如果引用计数减至0,则删除对象),并增加右操作数所指对象的引用计数的值。
最后,调用析构函数时,析构函数减少引用计数的值,如果计数减至0,则删除基础对象。
实现引用计数有两种经典策略:
一是引入辅助类(包含引用计数型),二是使用句柄类(分离引用计数型)。
策略1:
引用计数类
这个类的所有成员均为private。
我们不希望用户使用U_Ptr类,所以它没有任何public成员。
将HasPtr类设置为友元,使其成员可以访问U_Ptr的成员。
U_Ptr类保存指针和使用计数,每个HasPtr对象将指向一个U_Ptr对象,使用计数将跟踪指向每个U_Ptr对象的HasPtr对象的数目。
U_Ptr定义的仅有函数是构造函数和析构函数,构造函数复制指针,而析构函数删除它。
构造函数还将使用计数置为1,表示一个HasPtr对象指向这个U_Ptr对象。
classU_Ptr
{
friendclassHasPtr;
int*ip;
intuse;
U_Ptr(int*p):
ip(p){}
~U_Ptr()
{
deleteip;
}
};
classHasPtr
{
public:
HasPtr(int*p,inti):
_ptr(newU_Ptr(p)),_val(i)
{}
HasPtr(constHasPtr&obj):
_ptr(obj._ptr),_val(obj._val)
{
++_ptr->use;
}
HasPtr&operator=(constHasPtr&);
~HasPtr()
{
if(--_ptr->use==0)
delete_ptr;
}
private:
U_Ptr*_ptr;
int_val;
};
接受一个指针和一个int值的HasPtr构造函数使用其指针形参创建一个新的U_Ptr对象。
HasPtr构造函数执行完毕后,HasPtr对象指向一个新分配的U_Ptr对象,该U_Ptr对象存储给定指针。
新U_Ptr中的使用计数为1,表示只有一个HasPtr对象指向它。
复制构造函数从形参复制成员并增加使用计数的值。
复制构造函数执行完毕后,新创建对象与原有对象指向同一U_Ptr对象,该U_Ptr对象的使用计数加1。
析构函数将检查U_Ptr基础对象的使用计数。
如果使用计数为0,则这是最后一个指向该U_Ptr对象的HasPtr对象,在这种情况下,HasPtr析构函数删除其U_Ptr指针。
删除该指针将引起对U_Ptr析构函数的调用,U_Ptr析构函数删除int基础对象。
赋值与引用计数
首先将右操作数中的使用计数加1,然后将左操作数对象的使用计数减1并检查这个使用计数。
像析构函数中那样,如果这是指向U_Ptr对象的最后一个对象,就删除该对象,这会依次撤销int基础对象。
将左操作数中的当前值减1(可能撤销该对象)之后,再将指针从rhs复制到这个对象。
赋值照常返回对这个对象的引用。
HasPtr&HasPtr:
:
operator=(constHasPtr&rhs)
{
++rhs.ptr->use;//incrementusecountonrhsfirst
if(--ptr->use==0)
deleteptr;//ifusecountgoesto0onthisobject,deleteit
ptr=rhs.ptr;//copytheU_Ptrobject
val=rhs.val;//copytheintmember
return*this;
}
这个赋值操作符在减少左操作数的使用计数之前使rhs的使用计数加1,从而防止自身赋值。
如果左右操作数相同,赋值操作符的效果将是U_Ptr基础对象的使用计数加1之后立即减1。
值型类
复制值型对象时,会得到一个不同的新副本。
对副本所做的改变不会反映在原有对象上,反之亦然。
string类是值型类的一个例子。
要使指针成员表现得像一个值,复制HasPtr对象时必须复制指针所指向的对象:
复制构造函数不再复制指针,它将分配一个新的int对象,并初始化该对象以保存与被复制对象相同的值。
每个对象都保存属于自己的int值的不同副本。
因为每个对象保存自己的副本,所以析构函数将无条件删除指针。
赋值操作符不需要分配新对象,它只是必须记得给其指针所指向的对象赋新值,而不是给指针本身赋值。
//复制构造函数定义
HasPtr(constHasPtr&orig):
ptr(newint(*orig.ptr)),val(orig.val){}
//赋值函数定义
HasPtr&HasPtr:
:
operator=(constHasPtr&rhs)
{
*ptr=*rhs.ptr;//copythevaluepointedto
val=rhs.val;//copytheint
return*this;
}
策略2:
句柄类
C++中一个通用的技术是定义包装(cover)类或句柄类。
句柄类存储和管理基类指针。
指针所指对象的类型可以变化,它既可以指向基类类型对象又可以指向派生类型对象。
用户通过句柄类访问继承层次的操作。
因为句柄类使用指针执行操作,虚成员的行为将在运行时根据句柄实际绑定的对象的类型而变化。
因此,句柄的用户可以获得动态行为但无须操心指针的管理。
包装了继承层次的句柄有两个重要的设计考虑因素:
*像对任何保存指针的类一样,必须确定对复制控制做些什么。
包装了集成层次的句柄通常表现得像一个智能指针或者像一个值。
*句柄类决定句柄接口屏蔽还是不屏蔽继承层次,如果不屏蔽继承层次,用户必须了解和使用基本层次中的对象。
智能指针就是模拟指针动作的类。
所有的智能指针都会重载->和*操作符。
classSmart_Pointer
{
public:
//defaultconstructor:
unboundhandle
Smart_Pointer():
_p(0),_use(newstd:
:
size_t
(1)){}
//attachesahandletoacopyoftheBaseobject
Smart_Pointer(constBase&);
//copycontrolmemberstomanagetheusecountandpointers
Smart_Pointer(constSmart_Pointer&i):
_p(i._p),_use(i._use){++*use;}
~Smart_Pointer(){decr_use();}
Smart_Pointer&operator=(constSmart_Pointer&);
//memberaccessoperators
constBase*operator->()const
{
if(_p)
return_p;
else
throwstd:
:
logic_error("unboundBase");
}
constBase&operator*()const
{
if(_p)
return*p;
else
throwstd:
:
logic_error("unboundBase");
}
private:
Base*_p;
std:
:
size_t*_use;
voiddecr_use()
{
if(--*use==0)
{
delete_p;
delete_use;
}
}
};
OpenCV的Ptr模板类
OpenCV中的智能指针Ptr模板类就是采用分离引用计数型的句柄类实现技术。
以OpenCV的人脸识别为例,实现了人脸识别中的三种算法:
Eigenface、FisherFace和基于LBP特征的算法。
这三种算法也分别封装成三个类:
Eigenfaces、Fisherfaces、LBPH类,这三个类均派生自FaceRecognizer类,而FaceRecognizer类则派生自Algorithm类。
FaceRecognizer类是一个抽象基类。
OpenCV就是采用一个泛型句柄类Ptr管理FaceRecognizer类的对象。
template
{
public:
//!
emptyconstructor
Ptr();
//!
takeownershipofthepointer.Theassociatedreferencecounterisallocatedandsetto1
Ptr(_Tp*_obj);
//!
callsrelease()
~Ptr();
//!
copyconstructor.Copiesthemembersandcallsaddref()
Ptr(constPtr&ptr);
template
//!
copyoperator.Callsptr.addref()andrelease()beforecopyingthemembers
Ptr&operator=(constPtr&ptr);
//!
incrementsthereferencecounter
voidaddref();
//!
decrementsthereferencecounter.Ifitreaches0,delete_obj()iscalled
voidrelease();
//!
deletestheobject.Overrideifneeded
voiddelete_obj();
//!
returnstrueiffobj==NULL
boolempty()const;
//!
castpointertoanothertype
template
template
//!
helperoperatorsmaking"Ptr
_Tp*operator->();
const_Tp*operator->()const;
operator_Tp*();
operatorconst_Tp*()const;
_Tp*obj;// int*refcount;// }; 当创建一个FaceRecognizer的派生类Eigenfaces的对象时,我们把这个Eigenfaces对象放进Ptr对象内,就可以依赖句柄类Ptr确保Eigenfaces对象自动被释放。 Ptr 当利用createEigenFaceRecognizer动态创建一个Eigenfaces的对象后,立即把它放进Ptr 获得资源后立即放进管理对象,管理对象运用析构函数确保资源被释放。 Ptr { returnnewEigenfaces(num_components,threshold); } 我们注意到在createEigenFaceRecognizer实现源码中,返回了动态地创建Eigenfaces对象,并且隐式的转换成Ptr。 由C++的泛型句柄类思考OpenCV的Ptr模板类 时间 2013-03-2422: 44: 00 博客园-原创精华区 原文 主题 OpenCV C++ 泛型 OpenCV(计算机视觉库)2.4.4版本已经发布了,OpenCV发展到现在,由最初的C接口变成现在的C++接口,让开发者写程序越来越简单,接口越来越合理,也不用担心内存释放问题。 但要理解内部的一些实现机制,还真要费点功夫,这对开发者的C++基础要求越来越高。 本文就是笔者在做项目过程中的一点感悟,由C++泛型句柄类思考OpenCV的Ptr模板类的实现。 1、C++泛型句柄类 我们知道在包含指针成员的类中,需要特别注意类的复制控制,因为复制指针时只复制指针中的地址,而不会复制指针指向的对象。 这将导致当两个指针同时指向同一对象时,很可能一个指针删除了一对象,另一指针的用户还认为基础对象仍然存在,此时就出现了悬垂指针。 当类中有指针成员时,一般有两种方式来管理指针成员: 一是采用值型的方式管理 ,每个类对象都保留一份指针指向的对象的拷贝;另一种更好的方式是 使用智能指针 ,从而实现指针指向的对象的共享。 (可参看《C++Primer第四版》P419) 智能指针(smartpointer) 的一种通用实现技术是使用 引用计数(referencecount)。 智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。 每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的父本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。 智能指针实现引用计数有两种经典策略: 一是引入 辅助类(包含引用计数型) ,二是使用 句柄类(分离引用计数型) 。 ∙辅助类的解决方案是,定义一个单独的具体类来封装指针和相应的引用计数。 可参考我之前写的一个博客: 辅助类实现智能指针代码如下,参考《C++沉思录》,利用UPoint类作为辅助类封装了指针Point*和引用计数,从而代替指针Point*。 这个技术的主要思想是当多个Handle类的对象在堆上共享同一个Point*指向的内存区时,我们在这个内存区上多分配一点空间存放引用计数,那么我们就可以知道有多少个Handle类的对象在共享Point*指向的内存区,当引用计数为0时,我们就可以很放心的释放掉这块内存区,而不会出现悬垂指针了。 1//辅助类UPoint 2classUPoint{ 3private: 4friendclassHandle; 5intu; 6Pointp; 7UPoint(constPoint&pp): u (1),p(pp) 8{ 9 10} 11UPoint(intxx,intyy): p(xx,yy),u (1) 12{ 13 14} 15UPoint(): u (1) 16{ 17 18} 19}; 20 21classHandle{ 22public: 23Handle(): up(newUPoint) 24{ 25 26} 27Handle(intx,inty): up(newUPoint(x,y)) 28{ 29 30} 31Handle(constPoint&up): up(newUPoint(up)) 32{ 33 34} 35//复制构造函数 36Handle(constHandle&other): up(other.up) 37{ 38++up->u; 39} 40//赋值操作符 41Handle&operator=(constHandle&other) 42{ 43++other.up->u; 44if(--up->u==0){ 45deleteup; 46} 47up=other.up; 48return*this; 49} 50~Handle() 51{ 52if(--up->u==0){ 53deleteup; 54} 55} 56private: 57UPoint*up; 58}; 基于辅助类的智能指针实现方式需要创建一个依赖于Point类的新类,这样做对于特定的类而言是很好,但却让我们很难将句柄绑定到Point类派生的新类对象上。 因此,就有了分离引用计数型的句柄类实现了。 可参看《C++沉思录》P69页,OpenCV中的智能指针模板类Ptr就是基于这种计数实现。 下面是采用模板的方式实现的一个泛型句柄类(分离引用计数型),参考《C++Primer第四版》P561。 从下面可以看出辅助类消失了, 在这个句柄类中,我们用指向类型T的指针(共享对象,类似于上面的Point类型)和指向一个int的指针表示引用计数。 使用T*很重要,因为正是T*使我们不仅能够将一个Handle绑定到一个T类型的对象,还能将其绑定到一个继承自T的类的对象。 这个类模板的数据成员有两个: 指向某个实际需要管理的类型的数据的指针以及它的引用计数。 它定义了复制构造函数、赋值操作符以及解引、成员访问操作符。 其中解引操作符返回的是实际需要管理的数据,而箭头操作符返回的是这个指针。 这两个操作符使得我们操作Handle 1#ifndefHANDLE_H 2#defineHANDLE_H 3 4template 5{ 6public: 7//构造函数: 空指针 8Handle(T*p=0): ptr(p),use(newsize_t (1)){} 9//重载解引和箭头操作符 10T&operator*(); 11T*operator->(); 12constT&operator*()const; 13constT*operator->()const; 14//复制构造函数 15Handle(constHandle&h): ptr(h.ptr),use(h.use){++*use;} 16//重载赋值操作符 17Handle&operator=(constHandle&); 18//析构函数 19~Handle(){rem_ref();}; 20private: 21//共享的对象 22T*ptr; 23//引用计数 24size_t*use; 25//删除指针的具体函数 26voidrem_ref() 27{ 28if(--*use==0) 29{ 30deleteptr; 31deleteuse; 32} 33} 34}; 35 36template 37inlineHandle : operator=(constHandle&rhs) 38{ 39//右操作数引用计数+1 40++*rhs.use; 41//删除左操作数 42rem_ref(); 43//具体对象的赋值 44ptr=rhs.ptr; 45use=rhs.use; 46return*this; 47} 48 49template : operator*() 50{ 51if(ptr) 52return*ptr; 53//空指针时抛出异常 54throwstd: : runtime_error("dereferenceofunboundHandle"); 55} 56 57template : operator->() 58{ 59if(ptr) 60returnptr; 61//空指针时抛出异常 62throwstd: : runtime_error("accessthroughunboundHandle"); 63} 64 65template : operator*()const 66{ 67if(ptr) 68return*ptr; 69throwstd: : runtime_error("dereferenceofunboundHandle"); 70} 71 72template
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ 智能 指针