句柄类作用主要有两个,一是支持面向对象编程,实现多态性质;二是减少头文件的编译依赖关系,让文件间的编译更加独立。

  句柄类存储和管理基类指针,指针既可以指向基类类型对象又可以指向派生类型对象。用户通过句柄类访问继承层次的操作,用户可以获得动态行为,同时能够确保自动正确的销毁动态分配的对象,防止内存泄露。C++不能通过对象支持多态,而必须使用指针或引用。若保存基类的对象:派生类对象只有基类部分保存下来,而派生类部分被切掉;如果保存派生类的对象,基类对象无法有效转换为派生类对象。

  在Effective C++类的实现中条款22说明了为了实现接口和实现的分离,将对象的实现隐藏在指针身后,这样就能减少头文件的编译依赖关系。实现技术:使用引用数(referencecount)。句柄类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。计数器变化的情况:

  • 创建类的新对象时,初始化指针并将引用计数置为1;
  • 对象作为另一对象的副本,拷贝构造函数拷贝指针并增加与之相应的引用计数;
  • 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;
  • 调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除对象)。

  句柄类(智能指针smart point)是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。智能指针在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象。他们也可以用于跟踪被多用户共享的动态分配对象。
  在C++中一个通用的技术是定义包装(cover)类或句柄(handle)类,也称智能指针。句柄类存储和管理基类指针。指针所指向对象的类型可以变化,它既可以指向基类类型对象又可以指向派生类型对象。用户通过句柄类访问继承层次的操作(指针的对应的类型的操作)。因为句柄类使用指针执行操作,虚成员的行为将在运行时根据句柄实际绑定的对象类型而变化,即实现c++运行时动态绑定。故句柄用户可以获得动态行为但无需操心指针的管理。

  使用的相关技术:

引入使用计数

  定义句柄类或智能指针的通用技术是采用一个使用计数(use count)。句柄类将一个计数器与类指向的对象相关联。使用计数跟踪该类有多少个指针共享同一对象。当使用计数为0时,就删除该类对象,否则再删除类对象时,只要引用计数不为0,就不删除实际的类对象,而是是引用计数减1,实现虚删除。
使用计数类

  为了便于理解,我们定义一个实际类(Point),一个引用计数器类(UPoint),一个句柄类(Handle)。

  实现使用计数有两种经典策略:一种是定义一个单独的具体的类用以封装使用计数和指向实际类的指针;另一种是定义一个单独的具体的类用以封装引用计数和类的对象成员。我们称这种类为计数器类(UPoint)。在计数器类中,所有成员均设置为private,避免外部访问,但是将句柄类Handle类声明为自己的友元,从而使句柄类能操纵引用计数器。

写时复制

  写时复制(copy on write)技术是解决如何保证要改动的那个引用计数器类UPoint对象不能同时被任何其他的句柄类(Handle类)所引用。通俗的来说,就是当实际对象Point被多个Handle类的指针共享时,如果需要通过指针改变实际对象Point,而其他的指针又需要保持原来的值时,这就有矛盾了。打个不恰当的比方来说,两个以上的人共有5W块钱,如果其中一个人想用这5W块钱去消费,那就必须通知其他人。否则在这个人消费了5块钱后,其他人还以为他们仍然有5W块钱,如果这儿时候,他们去买5W的东西,就会发现钱变少了或是没有了,此时他们就陷入债务的泥团。在C++中通过指针访问已经删除或是不存在的对象,将是非常危险的。有可能系统提示该行为未定义,也有可以内存非法访问,还有可能使系统崩溃。

  具体地看代码:

class A
{public:A() {}~A() {}virtual void func() { std::cout << "A"; }
};
class B : public A
{public:B() {}~B() {}void func() { std::cout << "B"; }
}

  假设现在有容器vector<A> vec;,可以这样使用:

A a;
B b;
vec.push_back(a);
vec.push_back(b);

  但这样实际上把b转化为了它的基类A,假如:

vector<A>::iterator iter;
for (iter = vec.begin(); iter != vec.end(); iter++)
{(*iter).func();
}

  实际都会输出A,解决这个问题就是用指针代替对象(指针进行多态):vector<A*> vec;,这样可以达到多态的目的,但程序员必须接管内存的管理,例如:

B* b = new B;
vec.push_back(b);

  这时如果vec的生命周期结束了,它并不会主动释放b所占用的内存,不手动delete b;的话,就会产生内存泄漏。

  这个时候就有了句柄类的用武之地:

class A
{public:A() {}~A() {}virtual void func() const{std::cout << "A";}virtual A* clone() const { return new A(*this); }
};class B : public A
{public:B() {}~B() {}void func() const{std::cout << "B";}virtual B* clone() const { return new B(*this); }
};
class sample
{public:sample() :p(0), use(1) {}sample(const A& a) :p(a.clone()), use(1) {}sample(const sample& i) :p(i.p), use(i.use) { use++; }~sample() { decr_use(); }sample& operator= (const sample& i){use++;decr_use();p = i.p;use = i.use;return *this;}const A* operator->() const { if (p) return p; }const A& operator*() const { if (p) return *p; }
private:A* p;std::size_t use;void decr_use() { if (--use == 0) delete p; }
};int _tmain(int argc, _TCHAR* argv[])
{vector<sample> vec;A a;B b;sample sample1(a);sample sample2(b);vec.push_back(sample1);vec.push_back(sample2);vector<sample>::iterator iter;for (iter = vec.begin(); iter != vec.end(); iter++){(*iter)->func();}return 0;
}

  这样不但得到了正确的输出而且vec声明周期结束时,会自动调用析构函数释放内存。

  这里看到class A中使用了virtual A* clone() const { return new A(*this);}而不是virtual A* clone() const { return this;}是因为,后者存在问题,比如:

B b;
sample sam(b);
vec.push_back(sam);

  当b的生命周期结束后,vec生命周期尚未结束,调用(*iter)->func();会出错,这和直接用指针进行多台没有太大区别了。

参考:
1.C++句柄类(智能指针)小结
2.浅谈C++中句柄的使用


欢迎扫描二维码关注微信公众号 深度学习与数学   [每天获取免费的大数据、AI等相关的学习资源、经典和最新的深度学习相关的论文研读,算法和其他互联网技能的学习,概率论、线性代数等高等数学知识的回顾]

【一天一个C++小知识】005. C++中的句柄类(智能指针)相关推荐

  1. 【每天一个Python小知识】NumPy中的np.where

    函数形式:a = np.where(b) 功能:找到满足条件的b的索引a. 参数:b是某种条件,要求是np类型. 返回值:a是返回的索引,也是np类型. 举个套娃的例子来更好的了解这个函数: impo ...

  2. 【每天一个Python小知识】NumPy中的np.any

    import numpy as np np.any(np.array) 对矩阵中所有元素做或运算,存在True则返回True 一般在条件判等时使用,如: import numpy as np a = ...

  3. 安卓期末作品小项目_每日一个财务小知识——洞悉洞晰财务报告第一季

    财务报告 洞悉洞晰财务报告 目录 01/账务报告概述 02/资产负债表 03/利润表 04/现金流量表 05/所有者权益变动表 06/附注 一.财务报告概述 (一)财务报告及其目标 财务报告是指企业对 ...

  4. 【C#小知识】C#中一些易混淆概念总结(三)---------结构,GC,静态成员,静态类...

    目录: [C#小知识]C#中一些易混淆概念总结 [C#小知识]C#中一些易混淆概念总结(二) ---------------------------------------分割线----------- ...

  5. 【C#小知识】C#中一些易混淆概念总结(七)---------解析抽象类,抽象方法

    目录: [C#小知识]C#中一些易混淆概念总结--------数据类型存储位置,方法调用,out和ref参数的使用 [C#小知识]C#中一些易混淆概念总结(二)--------构造函数,this关键字 ...

  6. 奶粉中的php是什么,奶粉小知识:奶粉中的OPO起到什么作用?

    原标题:奶粉小知识:奶粉中的OPO起到什么作用? 现在不少奶粉都打着OPO的名号作为宣传卖点,那么,OPO在奶粉中到底到了什么作用,却仍然有着大部分人不太了解. 实际上,OPO,又称OPO结构脂,是一 ...

  7. 【每天一个Python小知识】用yaml的yaml.safe_load()方法读取配置文件中的参数

    文章目录 ymal安装 配置文件格式 配置文件读取 yaml是专门用来写配置文件的,因其简洁高效而被大众喜爱. ymal安装 python3安装: pip install pyyaml#python2 ...

  8. list赋值给另一个list_Python小知识: List的赋值方法,不能直接等于

    Python中关于对象复制有三种类型的使用方式,赋值.浅拷贝与深拷贝.他们既有区别又有联系,刚好最近碰到这一类的问题,研究下. 一.赋值 在python中,对象的赋值就是简单的对象引用,这点和C++不 ...

  9. 每天一个shell小知识(shell变量)

    目录 shell变量 自定义变量: 变量定义/查看 变量赋值的特殊操作: 双引号 单引号 反撇号 设置变量的作用范围: 特殊变量---环境变量: 位置变量: 预定义变量: shell变量 在各种she ...

  10. 每天一个shell小知识(for)

    目录 For循环语句 For语句的结构 结构 执行流程 实例 For循环语句 在实际工作环境中,经常会遇到某项任务需要多次执行的情况,而每次执行时仅仅是处理的对象不一样,其他命令完全相同.如:根据服务 ...

最新文章

  1. 代码文件的编码不统一导致的坑
  2. 织梦网站被黑客生成html,dedecms网站被挂马怎么处理
  3. css中width:100%与width:auto的区别
  4. mysql 导出select语句结果到excel文件等
  5. Qt 判断一个点是否落在三角形内(算法)
  6. noip模拟赛 radius
  7. 利用VBA在EXCLE2010和2007中找回2003式的经典菜单和工具栏
  8. 微信公众帐号开发教程第13篇-图文消息全攻略
  9. Mysql高可用方案mmm
  10. k3刷机 重置_斐讯K3刷机教程:一直重启、忘了密码怎么办?手机刷机包下载
  11. java聊天室课程报告_java课程设计报告(java聊天室).doc
  12. dede文章采集管理php,DEDECMSV5.7最新自动采集伪原创插件
  13. 深度篇——目标检测史(五) 细说 SSD 目标检测
  14. [随心译]2017.8.5-你家毛茸茸的宠物的荤粮正在加速气候变化
  15. C语言实现关机的小代码,不怎么完善,新人勿喷!
  16. 关于使用shopify 和theme 模版使用问题。
  17. 《少有人走的路》语录
  18. MD5与SHA加密算法
  19. 想天浏览器:推荐国内主流浏览器TOP10
  20. 引用类型不赋值跟赋null,调用的区别

热门文章

  1. [BZOJ4621]Tc605
  2. eslint配置文件解析
  3. Visual Basic 2012 借助DataGridView控件将Excel 2010数据导入到SQL server 2012
  4. Spring搭建MVC WEB项目[转]
  5. 3个开源TTS(五)eSpeak1.06的源码调试分析
  6. 百度地图,你必须知道的自定义Marker图标方法
  7. 经典排序算法(二)--桶排序Bucket Sort
  8. 归并排序MergeSort
  9. mysql数据库复习
  10. IDEA创建javaweb项目,及常见的请求和响应头