热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

OpenCV(计算机视觉库)2.4.4版本已经发布了,OpenCV发展到现在,由最初的C接口变成现在的C++接口,让开发者写程序越来越简单,接口越来越合理,也不用担心内存释放问题。但要理解内部的一些实

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

1、C++泛型句柄类                                                                                                                                                                                                                                                                        

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

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

 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& Handle::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::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::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::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::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 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 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 Ptr<_Tp2> ptr();
26 template const Ptr<_Tp2> ptr() const;
27
28 //! helper operators making "Ptr 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; //
36 int* refcount; //
37 };

Ptr模板类源文件:

View Code
  1 /////////////////////////////////// Ptr ////////////////////////////////////////
2
3 template inline Ptr<_Tp>::Ptr() : obj(0), refcount(0) {}
4 template 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 inline void Ptr<_Tp>::addref()
16 { if( refcount ) CV_XADD(refcount, 1); }
17
18 template 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 inline void Ptr<_Tp>::delete_obj()
30 {
31 if( obj ) delete obj;
32 }
33
34 template inline Ptr<_Tp>::~Ptr() { release(); }
35
36 template inline Ptr<_Tp>::Ptr(const Ptr<_Tp>& _ptr)
37 {
38 obj = _ptr.obj;
39 refcount = _ptr.refcount;
40 addref();
41 }
42
43 template 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 inline _Tp* Ptr<_Tp>::operator -> () { return obj; }
55 template inline const _Tp* Ptr<_Tp>::operator -> () const { return obj; }
56
57 template inline Ptr<_Tp>::operator _Tp* () { return obj; }
58 template inline Ptr<_Tp>::operator const _Tp*() const { return obj; }
59
60 template inline bool Ptr<_Tp>::empty() const { return obj == 0; }
61
62 template template 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 template 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 template 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::delete_obj();
116 template<> CV_EXPORTS void Ptr::delete_obj();
117 template<> CV_EXPORTS void Ptr::delete_obj();
118 template<> CV_EXPORTS void Ptr::delete_obj();
119 template<> CV_EXPORTS void Ptr::delete_obj();
120 template<> CV_EXPORTS void Ptr::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 model = createEigenFaceRecognizer(num_components, threshold);

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

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

 

 

 


推荐阅读
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
  • 本文分析了Wince程序内存和存储内存的分布及作用。Wince内存包括系统内存、对象存储和程序内存,其中系统内存占用了一部分SDRAM,而剩下的30M为程序内存和存储内存。对象存储是嵌入式wince操作系统中的一个新概念,常用于消费电子设备中。此外,文章还介绍了主电源和后备电池在操作系统中的作用。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 如何实现JDK版本的切换功能,解决开发环境冲突问题
    本文介绍了在开发过程中遇到JDK版本冲突的情况,以及如何通过修改环境变量实现JDK版本的切换功能,解决开发环境冲突的问题。通过合理的切换环境,可以更好地进行项目开发。同时,提醒读者注意不仅限于1.7和1.8版本的转换,还要适应不同项目和个人开发习惯的需求。 ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • 本文介绍了使用jQuery实现图片预加载和等比例缩放的方法,同时提供了演示和相关代码。该方法可以重置图片的宽度和高度,并使图片在水平和垂直方向上居中显示。 ... [详细]
author-avatar
爱蜜儿小秋秋
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有