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

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM