由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模板类源文件:

View Code
  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);

 

 

 

你可能感兴趣的:(opencv)