C++智能指针.docx

上传人:b****7 文档编号:9402068 上传时间:2023-02-04 格式:DOCX 页数:25 大小:26.08KB
下载 相关 举报
C++智能指针.docx_第1页
第1页 / 共25页
C++智能指针.docx_第2页
第2页 / 共25页
C++智能指针.docx_第3页
第3页 / 共25页
C++智能指针.docx_第4页
第4页 / 共25页
C++智能指针.docx_第5页
第5页 / 共25页
点击查看更多>>
下载资源
资源描述

C++智能指针.docx

《C++智能指针.docx》由会员分享,可在线阅读,更多相关《C++智能指针.docx(25页珍藏版)》请在冰豆网上搜索。

C++智能指针.docx

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类的对象。

templateclassCV_EXPORTSPtr

{

public:

//!

emptyconstructor

Ptr();

//!

takeownershipofthepointer.Theassociatedreferencecounterisallocatedandsetto1

Ptr(_Tp*_obj);

//!

callsrelease()

~Ptr();

//!

copyconstructor.Copiesthemembersandcallsaddref()

Ptr(constPtr&ptr);

templatePtr(constPtr<_Tp2>&ptr);

//!

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

templatePtr<_Tp2>ptr();

templateconstPtr<_Tp2>ptr()const;

//!

helperoperatorsmaking"Ptrptr"useverysimilarto"T*ptr".

_Tp*operator->();

const_Tp*operator->()const;

operator_Tp*();

operatorconst_Tp*()const;

_Tp*obj;//

int*refcount;//

};

当创建一个FaceRecognizer的派生类Eigenfaces的对象时,我们把这个Eigenfaces对象放进Ptr对象内,就可以依赖句柄类Ptr确保Eigenfaces对象自动被释放。

Ptrmodel=createEigenFaceRecognizer(num_components,threshold);

当利用createEigenFaceRecognizer动态创建一个Eigenfaces的对象后,立即把它放进Ptr中进行管理。

获得资源后立即放进管理对象,管理对象运用析构函数确保资源被释放。

PtrcreateEigenFaceRecognizer(intnum_components,doublethreshold)

{

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的对象就跟操作T的对象一样。

1#ifndefHANDLE_H

2#defineHANDLE_H

3

4templateclassHandle

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&Handle:

:

operator=(constHandle&rhs)

38{

39//右操作数引用计数+1

40++*rhs.use;

41//删除左操作数

42rem_ref();

43//具体对象的赋值

44ptr=rhs.ptr;

45use=rhs.use;

46return*this;

47}

48

49templateinlineT&Handle:

:

operator*()

50{

51if(ptr)

52return*ptr;

53//空指针时抛出异常

54throwstd:

:

runtime_error("dereferenceofunboundHandle");

55}

56

57templateinlineT*Handle:

:

operator->()

58{

59if(ptr)

60returnptr;

61//空指针时抛出异常

62throwstd:

:

runtime_error("accessthroughunboundHandle");

63}

64

65templateinlineconstT&Handle:

:

operator*()const

66{

67if(ptr)

68return*ptr;

69throwstd:

:

runtime_error("dereferenceofunboundHandle");

70}

71

72templateinlin

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 党团工作 > 入党转正申请

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1