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

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