转自:http://www.cnblogs.com/liu-jun/archive/2013/03/24/2979648.html
OpenCV(计算机视觉库)2.4.4版本已经发布了,OpenCV发展到现在,由最初的C接口变成现在的C++接口,让开发者写程序越来越简单,接口越来越合理,也不用担心内存释放问题。但要理解内部的一些实现机制,还真要费点功夫,这对开发者的C++基础要求越来越高。本文就是笔者在做项目过程中的一点感悟,由C++泛型句柄类思考OpenCV的Ptr模板类的实现。
1、C++泛型句柄类
我们知道在包含指针成员的类中,需要特别注意类的复制控制,因为复制指针时只复制指针中的地址,而不会复制指针指向的对象。这将导致当两个指针同时指向同一对象时,很可能一个指针删除了一对象,另一指针的用户还认为基础对象仍然存在,此时就出现了悬垂指针。
- 辅助类的解决方案是,定义一个单独的具体类来封装指针和相应的引用计数。可参考我之前写的一个博客:http://www.cnblogs.com/liu-jun/archive/2013/03/01/2939396.html
辅助类实现智能指针代码如下,参考《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模板类源文件:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
可以看出,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);