c++ 容器、继承层次、句柄类
一、容器与继承
在容器中保存有继承关系的对象,如果定义成保存基类对象,则派生类将被切割,如果定义成保存派生类对象,则保存基类对象又成问题(基类对象将被强制转换成派生类对象,而派生类中定义的成员未被初始化)。
唯一的可行的选择是容器中保存对象的指针。但是需要用户管理对象和指针。C++中一个通用的技术是包装类(cover)或句柄类(handle)。用句柄类存储和管理类指针。
句柄类大体上完成两方面的工作:
a,管理指针,这与智能指针的功能类似。
b,实现多态,利用动态绑定,是得指针既可以指向基类,也可以指向派生类。
包装了继承层次的句柄有两个重要的设计考虑因素:
1)像对任何保存指针的类一样,必须确定对复制控件做些什么。包装了继承层次的句柄通常表现得像一个智能指针或者像一个值。
2)名柄类决定句柄接口屏蔽还是不屏蔽继承层次,如果不屏蔽继承层次,用户必须了解和使用基本层次中的对象(objects in theunderlying hierarchy)。
下面通过一个我自己写的一个简单的例子来说明这个问题:
这个例子大体思路是:程序包括一个基类,一个派生类,还有一个句柄类。
其中,基类有2个私有成员,数值m_base和程序名字name。派生类有一个新的私有成员,m_der。
派生类和基类有虚函数compute。,基类的compute它计算基类成员m_base平方。派生类的compute计算m_base平方和m_der之和。
句柄类有两个数据成员,分别是指向引用计数的指针和指向基类(或者是其派生类的指针)。
#include <iostream>
#include <string>
#include <exception>
#include <stdexcept>
using namespace std;//基类
class Base
{
public:Base(int a_=1, string na="Base"):m_base(a_),name(na) {}Base(const Base &rhs)//复制构造{m_base = rhs.m_base;name = rhs.name;}//clone函数来返回一个自身的副本virtual Base* clone() const{return new Base(*this);}const string getName() const//返回对象名字{return name;}virtual int compute()const// 返回整数成员的平方{return m_base*m_base;}
private:int m_base;string name;
};// 派生类
class Derived:public Base
{
public:Derived(int a=2,string na="Derived", int b=2):Base(a,na),m_der(b) {}Derived(const Derived &rhs):Base(rhs) //copy constructor{m_der = rhs.m_der;}Derived* clone() const{return new Derived(*this);}int compute()const//返回成员平方和加上派生类新成员{return Base::compute() + m_der;}
private:int m_der;
};//句柄类
class Handle
{
public://默认构造函数 1//指针置0,不与任何对象关联,计数器初始化为1Handle():pBase(0),use(new int(1)) {}//析构函数~Handle(){des_use();}//接受Base对象 item的构造函数,注意引用。 2Handle(const Base &item):pBase(item.clone()),use(new int(1)) {}//复制控制函数:管理计数器和指针。 3Handle(const Handle &ha):pBase(ha.pBase),use(ha.use){++*use;}//赋值操作符Handle& operator=(const Handle& rhs){++*rhs.use;//右操作数的引用计数+1des_use();//处理左操作数//复制右操作数pBase = rhs.pBase;use = rhs.use;//返回左操作数的引用return *this;}//重载箭头操作符const Base* operator->() const{if(pBase)return pBase;elsethrow logic_error("unbound Handle");}//重载解引用操作符const Base &operator*() const{if(pBase)return *pBase;elsethrow logic_error("unbound Handle");}// 输出引用计数void print_use(){cout<<pBase->getName()<<" use count "<<*use<<endl;}
private://指向引用计数int *use;//指向基类的指针,也可以用来指向派生类Base *pBase;void des_use(){if(--*use == 0){cout<<pBase->getName()<<" delete\n";delete pBase;delete use;}}
};int main()
{Handle h1(Base(2,"Base"));h1.print_use();cout<<"Base compute:"<<(*h1).compute()<<endl;Handle h2(h1);h2.print_use();cout<<"Base compute:"<<h2->compute()<<endl;cout<<"*------------------------------------------*\n";Handle h3(Derived(3, "derive", 3));h1 = h3;h1.print_use();cout<<"Derive compute:"<<(*h1).compute()<<endl;return 0;
}
二、句柄类
句柄类Handle 有3个构造函数:默认构造函数,复制构造函数,和接收基类Base对象的构造函数。第2个构造函数复制Base类对象保证只有句柄对象
存在副本就存在。其中第2个构造函数的要难。我们希望句柄的用户创建自己的对象,在这些对象上关联句柄。构造函数将分配适当类型的新对象并将
形参复制到新分配的对象中,这样句柄类Handle将拥有对象并 能保证 在关联到该对象的最后一个Handle 对象消失之前不会删除该对象。
复制未知类型。 句柄类经常需要在不知道对象确切类型时分配已知对象的新副本,解决这个问题的通用方法是定义虚操作进行复制,通常命名为clone。
如上面代码中,为了支持句柄类,需要从基类开始,从继承层次的每个类中增加clone,并且基类必须把这个函数定义为虚函数,每个类必须重新定义
这个虚函数,因为函数的存在是为了生成类对象的新副本,所以定义返回类型为类本身,如下面的代码。
//clone函数来返回一个自身的副本virtual Base* clone() const{return new Base(*this);}
返回类本身类型,且为虚函数。
//接受Base对象 item的构造函数Handle(const Base &item):pBase(item.clone()),use(new int(1)) {}
这个clone函数必须为虚函数,因为句柄类经常需要在不知道对象的确切类型时分配已知对象的新副本,我们知道,c++ 动态绑定只对 虚函数,且在指针或者引用
时才发生。要解决 复制未知类型 问题只能通过定义一个函数其参数是基类的引用,然后调用一个克隆自身的函数,当这个函数为虚函数时就能发生动态绑定,从而
在不知道 对象本身类型时 我们可以 为这个对象分配一个新副本 并指向它。
整个程序代码运行效果如下:
int main()
{Handle h1(Base(2,"Base"));h1.print_use();cout<<"Base compute:"<<(*h1).compute()<<endl;Handle h2(h1);h2.print_use();cout<<"Base compute:"<<h2->compute()<<endl;cout<<"*------------------------------------------*\n";Handle h3(Derived(3, "derive", 3));h1 = h3;h1.print_use();cout<<"Derive compute:"<<(*h1).compute()<<endl;return 0;
}
从main函数里我们可以看出,句柄类Handle 首先构造一个存储基类对象的句柄,然后再调用3创建新对象,这个句柄依然指向基类对象,所以引用计数为2.
然后再构造句柄h3 其构造对象为派生类对象,再把h2 赋值给h1,此时h1句柄 指向了派生了对象的句柄。
我们可以利用 STL 的容器 保存因继承关联的对象,就是利用上面的句柄类。具体不在详述。
参考资料<c++ primer>
c++ 容器、继承层次、句柄类相关推荐
- C++ Primer 学习笔记_72_面向对象编程 --句柄类与继承[续]
面向对象编程 --句柄类与继承[续] 三.句柄的使用 使用Sales_item对象能够更easy地编写书店应用程序.代码将不必管理Item_base对象的指针,但仍然能够获得通过Sales_item对 ...
- 创建一个银行账户的继承层次,表示银行的所有客户的账户。每个客户都能在他们的银行账户存钱,取钱。但是账户可以分为更具体的两种类型,例如,依靠存款生息的存储账户SavingsAccount类
关联与继承练习题 题目: 源码: Account(账户)类: CheckingAccount(信用卡)类: SavingsAccount(借记卡)类: Person(用户)类: Transaction ...
- 创建一个银行账户的继承层次,表示银行的所有客户的账户。每个客户都能在他们的银行账户存钱,取钱。但是账户可以分为更具体的两种类型,例如,依靠存款生息的存储账户SavingsAccount类,另一种就是信
不要自卑,去提升实力 互联网行业谁技术牛谁是爹 如果文章可以带给你能量,那是最好的事!请相信自己 加油o~ 创建一个银行账户的继承层次,表示银行的所有客户的账户.每个客户都能在他们的银行账户存钱,取钱 ...
- 【c++手记】句柄类智能指针
很多同学学习c++都会看的一本经典教材<Primer> 而在面向对象里面提及到一种概念-智能指针,而往往同学会出现以下的问题 [问题] 智能指针是不是一种指针? stl里面的智能指针是什么 ...
- [C++ Primer] 第十五章的句柄类
1 为什么要使用句柄类? 句柄?windows中的句柄吗?那不是一个整数吗?不是,当然不是.句柄类应该算是C++中的一种技术,一种管理指针的技术,一种实现面向对象编程的技术. 为什么要使用句柄呢? 多 ...
- Matlab中句柄类和值类的比较
句柄类和值类的比较 基本差异 值类构造函数返回一个与其赋值变量相关联的对象.如果对此变量重新赋值,MATLAB® 会创建原始对象的独立副本.如果将此变量传递给函数以修改它,函数必须将 ...
- C++知识点61——typename与class、模板编程与继承、模板类和友元、类模板与static成员
一.typename与class的异同 1.啥时候既可以使用typename,又可以使用class? 当表示模板参数的时候,二者没有区别 2.啥时候只能使用typename,不能使用class? 当模 ...
- 如何为HTML容器分配多个类?
本文翻译自:How to assign multiple classes to an HTML container? Is it possible to assign multiple classes ...
- 虚继承c语言例子,C/C++ 多继承{虚基类,虚继承,构造顺序,析构顺序}
C/C++:一个基类继承和多个基类继承的区别 1.对多个基类继承会出现类之间嵌套时出现的同名问题,如果同名变量或者函数出现不在同一层次,则底层派生隐藏外层比如继承基类的同名变量和函数,不会出现二义性, ...
- C++虚继承(七) --- 虚继承对基类构造函数调用顺序的影响
继承作为面向对象编程的一种基本特征,其使用频率非常高.而继承包含了虚拟继承和普通继承,在可见性上分为public.protected.private.可见性继承比较简单,而虚拟继承对学习c++的难度较 ...
最新文章
- Keil中使用宏编译来定义DEBUG输出
- 常用深度学习模型介绍(1)
- 【运筹学】表上作业法 ( 示例 | 使用 “ 最小元素法 “ 找初始基可行解 )
- springmvc二十五:springmvc支持ajax
- Microsoft CryptoAPI加密技术(一)
- 多线程编程:阻塞、并发队列的使用总结
- ext4 关闭延迟分配
- linux fpga 开发环境,- Vivado+Zedboard之Linux开发环境搭建
- [导入]Mobile Media API概述
- rootfs文件系统的制作(二)
- Windows API Unicode 和 多字节转化demo
- OFFICE技术讲座:JDK绘制旋转字体的效果(水平)
- OpenCV-图像处理(19、Canny边缘检测)
- 怎么压缩jpg图片到100k?jpg图片怎么压缩?
- 解决:UnsatisfiedDependencyException
- myeclipse安装插件phpeclipse后进行PHP代码编写
- android 添加蒙版实现护眼模式(夜间模式)
- 工商管理专业的毕业论文怎么选题?
- 京东登录页面(静态页面)
- blackduck issue fix