由C++的泛型句柄类思考OpenCV的Ptr模板类


OpenCV(计算机视觉库)2.4.4版本已经发布了,OpenCV发展到现在,由最初的C接口变成现在的C++接口,让开发者写程序越来越简单,接口越来越合理,也不用担心内存释放问题。但要理解内部的一些实现机制,还真要费点功夫,这对开发者的C++基础要求越来越高。本文就是笔者在做项目过程中的一点感悟,由C++泛型句柄类思考OpenCV的Ptr模板类的实现。

1、C++泛型句柄类                                                                                                                                                                                                                                                                        

我们知道在包含指针成员的类中,需要特别注意类的复制控制,因为复制指针时只复制指针中的地址,而不会复制指针指向的对象。这将导致当两个指针同时指向同一对象时,很可能一个指针删除了一对象,另一指针的用户还认为基础对象仍然存在,此时就出现了悬垂指针。

当类中有指针成员时,一般有两种方式来管理指针成员: 一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更好的方式是 使用智能指针,从而实现指针指向的对象的共享。(可参看《C++ Primer第四版》P419)
 
智能指针(smart pointer)的一种通用实现技术是使用 引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。
每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的父本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。
 
智能指针实现引用计数有两种经典策略:一是引入 辅助类(包含引用计数型),二是使用 句柄类(分离引用计数型)

辅助类实现智能指针代码如下,参考《C++沉思录》,利用UPoint类作为辅助类封装了指针Point*和引用计数,从而代替指针Point*。这个技术的主要思想是当多个Handle类的对象在堆上共享同一个Point*指向的内存区时,我们在这个内存区上多分配一点空间存放引用计数,那么我们就可以知道有多少个Handle类的对象在共享Point*指向的内存区,当引用计数为0时,我们就可以很放心的释放掉这块内存区,而不会出现悬垂指针了。

1  // 辅助类UPoint
2  class UPoint{ 
3  private
4 friend  class Handle; 
5  int u; 
6 Point p; 
7 UPoint( const Point& pp):u( 1),p(pp) 
8 { 
9 
10 } 
11 UPoint( int xx, int yy):p(xx,yy),u( 1
12 { 
13 
14 } 
15 UPoint():u( 1
16 { 
17 
18 } 
19 }; 
20 
21  class Handle{ 
22  public
23 Handle():up( new UPoint) 
24 { 
25 
26 } 
27 Handle( int x, int y):up( new UPoint(x,y)) 
28 { 
29 
30 } 
31 Handle( const Point& up):up( new UPoint(up))
32 { 
33 
34 } 
35  // 复制构造函数
36 Handle( const Handle& other):up(other.up) 
37 { 
38 ++up->u; 
39 } 
40  // 赋值操作符
41 Handle&  operator=( const Handle& other) 
42 { 
43 ++other.up->u; 
44  if(--up->u== 0){ 
45 delete up; 
46 } 
47 up = other.up; 
48  return * this
49 } 
50 ~Handle() 
51 { 
52  if(--up->u ==  0){ 
53 delete up; 
54 } 
55 } 
56  private
57 UPoint *up; 
58 };

基于辅助类的智能指针实现方式需要创建一个依赖于Point类的新类,这样做对于特定的类而言是很好,但却让我们很难将句柄绑定到Point类派生的新类对象上。因此,就有了分离引用计数型的句柄类实现了。可参看《C++ 沉思录》P69页,OpenCV中的智能指针模板类Ptr就是基于这种计数实现。
下面是采用模板的方式实现的一个泛型句柄类(分离引用计数型),参考《C++ Primer第四版》P561。从下面可以看出辅助类消失了,在这个句柄类中,我们用指向类型T的指针(共享对象,类似于上面的Point类型)和指向一个int的指针表示引用计数。使用T*很重要,因为正是T*使我们不仅能够将一个Handle绑定到一个T类型的对象,还能将其绑定到一个继承自T的类的对象。

这个类模板的数据成员有两个:指向某个实际需要管理的类型的数据的指针以及它的引用计数。它定义了复制构造函数、赋值操作符以及解引、成员访问操作符。其中解引操作符返回的是实际需要管理的数据,而箭头操作符返回的是这个指针。这两个操作符使得我们操作Handle<T>的对象就跟操作T的对象一样。

1 #ifndef HANDLE_H
2  #define HANDLE_H
3 
4 template < class T>  class Handle
5 {
6  public:
7  // 构造函数:空指针
8 Handle(T *p =  0):ptr(p),use( new size_t( 1)){}
9  // 重载解引和箭头操作符
10 T&  operator*();
11 T*  operator->();
12  const T&  operator*() const;
13  const T*  operator->() const;
14  // 复制构造函数
15 Handle( const Handle& h):ptr(h.ptr),use(h.use){++*use;}
16  // 重载赋值操作符
17 Handle&  operator=( const Handle&);
18  // 析构函数
19 ~Handle(){rem_ref();};
20  private:
21  // 共享的对象
22 T *ptr;
23  // 引用计数
24 size_t *use;
25  // 删除指针的具体函数
26  void rem_ref()
27 {
28  if(--*use ==  0)
29 {
30 delete ptr;
31 delete use;
32 }
33 }
34 };
35 
36 template< class T>
37 inline Handle<T>& Handle<T>:: operator=( const Handle &rhs)
38 {
39  // 右操作数引用计数+1
40 ++*rhs.use;
41  // 删除左操作数
42 rem_ref();
43  // 具体对象的赋值
44 ptr = rhs.ptr;
45 use = rhs.use;
46  return * this;
47 }
48 
49 template < class T> inline T& Handle<T>:: operator*()
50 {
51  if(ptr)
52  return *ptr;
53  // 空指针时抛出异常
54  throw std::runtime_error( " dereference of unbound Handle ");
55 }
56 
57 template < class T> inline T* Handle<T>:: operator->()
58 {
59  if(ptr)
60  return ptr;
61  // 空指针时抛出异常
62  throw std::runtime_error( " access through unbound Handle ");
63 }
64 
65 template < class T> inline  const T& Handle<T>:: operator*() const
66 {
67  if(ptr)
68  return *ptr;
69  throw std::runtime_error( " dereference of unbound Handle ");
70 }
71 
72 template < class T> inline  const T* Handle<T>:: operator->() const
73 {
74  if(ptr)
75  return ptr;
76  throw std::runtime_error( " access through unbound Handle "); 
77 }
78 
79  #endif

2、OpenCV中的智能指针模板类Ptr                                                                                                                                                                                           

以上了解了C++中的泛型句柄类实现后,我们来看看OpenCV中怎么利用泛型句柄类来管理OpenCV中的资源。

在OpenCV2.4.2后,添加了FaceRecognizer这个人脸识别类。其中实现了人脸识别中的三种算法:Eigenface、FisherFace和基于LBP特征的算法。这三种算法也分别封装成三个类:Eigenfaces、Fisherfaces、LBPH类,这三个类均派生自FaceRecognizer类,而FaceRecognizer类则派生自Algorithm类。FaceRecognizer类是一个抽象基类,它的头文件在contrib.hpp中(以OpenCV2.4.4为例),下面是它的头文件。

1  class CV_EXPORTS_W FaceRecognizer :  public Algorithm
2 {
3  public:
4  // ! virtual destructor
5  virtual ~FaceRecognizer() {}
6 
7  //  Trains a FaceRecognizer.
8 CV_WRAP  virtual  void train(InputArrayOfArrays src, InputArray labels) =  0;
9 
10  //  Updates a FaceRecognizer.
11 CV_WRAP  void update(InputArrayOfArrays src, InputArray labels);
12 
13  //  Gets a prediction from a FaceRecognizer.
14  virtual  int predict(InputArray src)  const =  0;
15 
16  //  Predicts the label and confidence for a given sample.
17 CV_WRAP  virtual  void predict(InputArray src, CV_OUT  int &label, CV_OUT  double &confidence)  const =  0;
18 
19  //  Serializes this object to a given filename.
20 CV_WRAP  virtual  void save( const  string& filename)  const;
21 
22  //  Deserializes this object from a given filename.
23 CV_WRAP  virtual  void load( const  string& filename);
24 
25  //  Serializes this object to a given cv::FileStorage.
26  virtual  void save(FileStorage& fs)  const =  0;
27 
28  //  Deserializes this object from a given cv::FileStorage.
29  virtual  void load( const FileStorage& fs) =  0;
30 
31 };

以人脸识别FaceRecognizer类为例,OpenCV就是采用一个泛型句柄类Ptr管理FaceRecognizer类的对象。先来看看OpenCV中的Ptr类是怎么实现的。OpenCV中的Ptr模板类头文件在core.hpp(以OpenCV2.4.4为例),源文件则在operations.hpp(以OpenCV2.4.4为例)。

Ptr模板类头文件:

1 template<typename _Tp>  class CV_EXPORTS Ptr
2 {
3  public:
4  // ! empty constructor
5 Ptr();
6  // ! take ownership of the pointer. The associated reference counter is allocated and set to 1
7 Ptr(_Tp* _obj);
8  // ! calls release()
9 ~Ptr();
10  // ! copy constructor. Copies the members and calls addref()
11 Ptr( const Ptr& ptr);
12 template<typename _Tp2> Ptr( const Ptr<_Tp2>& ptr);
13  // ! copy operator. Calls ptr.addref() and release() before copying the members
14 Ptr&  operator = ( const Ptr& ptr);
15  // ! increments the reference counter
16  void addref();
17  // ! decrements the reference counter. If it reaches 0, delete_obj() is called
18  void release();
19  // ! deletes the object. Override if needed
20  void delete_obj();
21  // ! returns true iff obj==NULL
22  bool empty()  const;
23 
24  // ! cast pointer to another type
25 template<typename _Tp2> Ptr<_Tp2> ptr();
26 template<typename _Tp2>  const Ptr<_Tp2> ptr()  const;
27 
28  // ! helper operators making "Ptr<T> ptr" use very similar to "T* ptr".
29 _Tp*  operator -> ();
30  const _Tp*  operator -> ()  const;
31 
32  operator _Tp* ();
33  operator  const _Tp*()  const;
34 
35 _Tp* obj;  // < the object pointer.
36  int* refcount;  // < the associated reference counter
37 };

Ptr模板类源文件:

1  / // Ptr  /// /
2 
3 template<typename _Tp> inline Ptr<_Tp>::Ptr() : obj( 0), refcount( 0) {}
4 template<typename _Tp> inline Ptr<_Tp>::Ptr(_Tp* _obj) : obj(_obj)
5 {
6  if(obj)
7 {
8 refcount = ( int*)fastMalloc( sizeof(*refcount));
9 *refcount =  1;
10 }
11  else
12 refcount =  0;
13 }
14 
15 template<typename _Tp> inline  void Ptr<_Tp>::addref()
16 {  if( refcount ) CV_XADD(refcount,  1); }
17 
18 template<typename _Tp> inline  void Ptr<_Tp>::release()
19 {
20  if( refcount && CV_XADD(refcount, - 1) ==  1 )
21 {
22 delete_obj();
23 fastFree(refcount);
24 }
25 refcount =  0;
26 obj =  0;
27 }
28 
29 template<typename _Tp> inline  void Ptr<_Tp>::delete_obj()
30 {
31  if( obj ) delete obj;
32 }
33 
34 template<typename _Tp> inline Ptr<_Tp>::~Ptr() { release(); }
35 
36 template<typename _Tp> inline Ptr<_Tp>::Ptr( const Ptr<_Tp>& _ptr)
37 {
38 obj = _ptr.obj;
39 refcount = _ptr.refcount;
40 addref();
41 }
42 
43 template<typename _Tp> inline Ptr<_Tp>& Ptr<_Tp>:: operator = ( const Ptr<_Tp>& _ptr)
44 {
45  int* _refcount = _ptr.refcount;
46  if( _refcount )
47 CV_XADD(_refcount,  1);
48 release();
49 obj = _ptr.obj;
50 refcount = _refcount;
51  return * this;
52 }
53 
54 template<typename _Tp> inline _Tp* Ptr<_Tp>:: operator -> () {  return obj; }
55 template<typename _Tp> inline  const _Tp* Ptr<_Tp>:: operator -> ()  const {  return obj; }
56 
57 template<typename _Tp> inline Ptr<_Tp>:: operator _Tp* () {  return obj; }
58 template<typename _Tp> inline Ptr<_Tp>:: operator  const _Tp*()  const {  return obj; }
59 
60 template<typename _Tp> inline  bool Ptr<_Tp>::empty()  const {  return obj ==  0; }
61 
62 template<typename _Tp> template<typename _Tp2> Ptr<_Tp>::Ptr( const Ptr<_Tp2>& p)
63 : obj( 0), refcount( 0)
64 {
65  if (p.empty())
66  return;
67 
68 _Tp* p_casted = dynamic_cast<_Tp*>(p.obj);
69  if (!p_casted)
70  return;
71 
72 obj = p_casted;
73 refcount = p.refcount;
74 addref();
75 }
76 
77 template<typename _Tp> template<typename _Tp2> inline Ptr<_Tp2> Ptr<_Tp>::ptr()
78 {
79 Ptr<_Tp2> p;
80  if( !obj )
81  return p;
82 
83 _Tp2* obj_casted = dynamic_cast<_Tp2*>(obj);
84  if (!obj_casted)
85  return p;
86 
87  if( refcount )
88 CV_XADD(refcount,  1);
89 
90 p.obj = obj_casted;
91 p.refcount = refcount;
92  return p;
93 }
94 
95 template<typename _Tp> template<typename _Tp2> inline  const Ptr<_Tp2> Ptr<_Tp>::ptr()  const
96 {
97 Ptr<_Tp2> p;
98  if( !obj )
99  return p;
100 
101 _Tp2* obj_casted = dynamic_cast<_Tp2*>(obj);
102  if (!obj_casted)
103  return p;
104 
105  if( refcount )
106 CV_XADD(refcount,  1);
107 
108 p.obj = obj_casted;
109 p.refcount = refcount;
110  return p;
111 }
112 
113  /// / specializied implementations of Ptr::delete_obj() for classic OpenCV types
114 
115 template<> CV_EXPORTS  void Ptr<CvMat>::delete_obj();
116 template<> CV_EXPORTS  void Ptr<IplImage>::delete_obj();
117 template<> CV_EXPORTS  void Ptr<CvMatND>::delete_obj();
118 template<> CV_EXPORTS  void Ptr<CvSparseMat>::delete_obj();
119 template<> CV_EXPORTS  void Ptr<CvMemStorage>::delete_obj();
120 template<> CV_EXPORTS  void Ptr<CvFileStorage>::delete_obj();

可以看出,OpenCV中的智能指针Ptr模板类就是采用分离引用计数型的句柄类实现技术。

下面来看看在OpenCV中怎么应用Ptr模板类来管理OpenCV的资源对象,以人脸识别FaceRecognizer类为例。

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

 

1  //  Let''s say we want to keep 10 Eigenfaces and have a threshold value of 10.0
2  int num_components =  10;
3  double threshold =  10.0;
4  //  Then if you want to have a cv::FaceRecognizer with a confidence threshold,
5  //  create the concrete implementation with the appropiate parameters:
6 Ptr<FaceRecognizer> model = createEigenFaceRecognizer(num_components, threshold);

第6行的createEigenFaceRecognizer函数的源码在facerec.cpp中,如下所示。

1 Ptr<FaceRecognizer> createEigenFaceRecognizer( int num_components,  double threshold)
2 {
3  return  new Eigenfaces(num_components, threshold);
4 }

 我们来分析下上面两段代码,其中Ptr<FaceRecognizer> model = createEigenFaceRecognizer(num_components, threshold);
当利用createEigenFaceRecognizer动态创建一个Eigenfaces的对象后,立即把它放进Ptr<FaceRecognizer>中进行管理。这和《Effective C++:55 Specific Ways to Improve Your Programs and Designe 3rd Edition》中的条款13不谋而合,即获得资源后立即放进管理对象,管理对象运用析构函数确保资源被释放。

我们注意到在createEigenFaceRecognizer实现源码中,返回了动态创建地Eigenfaces对象,并且隐式的转换成Ptr<FaceRecognizer>。(注意:这里的隐式转换依赖于Ptr<FaceRecognizer>的构造函数,相关知识可参考《C++ Primer第四版》P394页

这个返回智能指针的设计非常好,在《Effective C++:55 Specific Ways to Improve Your Programs and Designe 3rd Edition》中条款18中提到:任何接口如果要求客户必须记得某些事情,就有着“不正确使用”的倾向,因为客户可能会忘记做那件事。

这里也一样,万一客户(即使用OpenCV库的开发者)忘记使用智能指针Ptr类怎么办?因此,许多时候,较佳接口的设计原则采用先发制人,这里就是令createEigenFaceRecognizer返回一个智能指针Ptr<FaceRecognizer>。

这样,开发者就必须采用这样的方式来创建Eigenfaces对象Ptr<FaceRecognizer> model = createEigenFaceRecognizer(num_components, threshold);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值