一、容器与继承

在容器中保存有继承关系的对象,如果定义成保存基类对象,则派生类将被切割,如果定义成保存派生类对象,则保存基类对象又成问题(基类对象将被强制转换成派生类对象,而派生类中定义的成员未被初始化)。

唯一的可行的选择是容器中保存对象的指针。但是需要用户管理对象和指针。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之和。

句柄类有两个数据成员,分别是指向引用计数的指针和指向基类(或者是其派生类的指针)。

  1. #include <iostream>
  2. #include <string>
  3. #include <exception>
  4. #include <stdexcept>
  5. using namespace std;
  6.  
  7. //基类
  8. class Base
  9. {
  10. public:
  11. Base(int a_=1, string na="Base"):m_base(a_),name(na) {}
  12. Base(const Base &rhs)//复制构造
  13. {
  14. m_base = rhs.m_base;
  15. name = rhs.name;
  16. }
  17. //clone函数来返回一个自身的副本
  18. virtual Base* clone() const
  19. {
  20. return new Base(*this);
  21. }
  22. const string getName() const//返回对象名字
  23. {
  24. return name;
  25. }
  26. virtual int compute()const// 返回整数成员的平方
  27. {
  28. return m_base*m_base;
  29. }
  30. private:
  31. int m_base;
  32. string name;
  33. };
  34.  
  35. // 派生类
  36. class Derived:public Base
  37. {
  38. public:
  39. Derived(int a=2,string na="Derived", int b=2):Base(a,na),m_der(b) {}
  40. Derived(const Derived &rhs):Base(rhs) //copy constructor
  41. {
  42. m_der = rhs.m_der;
  43. }
  44. Derived* clone() const
  45. {
  46. return new Derived(*this);
  47. }
  48.  
  49. int compute()const//返回成员平方和加上派生类新成员
  50. {
  51. return Base::compute() + m_der;
  52. }
  53. private:
  54. int m_der;
  55. };
  56.  
  57. //句柄类
  58. class Handle
  59. {
  60. public:
  61. //默认构造函数 1
  62. //指针置0,不与任何对象关联,计数器初始化为1
  63. Handle():pBase(0),use(new int(1)) {}
  64. //析构函数
  65. ~Handle()
  66. {
  67. des_use();
  68. }
  69. //接受Base对象 item的构造函数,注意引用。 2
  70. Handle(const Base &item):pBase(item.clone()),use(new int(1)) {}
  71. //复制控制函数:管理计数器和指针。 3
  72. Handle(const Handle &ha):pBase(ha.pBase),use(ha.use)
  73. {
  74. ++*use;
  75. }
  76. //赋值操作符
  77. Handle& operator=(const Handle& rhs)
  78. {
  79. ++*rhs.use;//右操作数的引用计数+1
  80. des_use();//处理左操作数
  81. //复制右操作数
  82. pBase = rhs.pBase;
  83. use = rhs.use;
  84. //返回左操作数的引用
  85. return *this;
  86. }
  87. //重载箭头操作符
  88. const Base* operator->() const
  89. {
  90. if(pBase)
  91. return pBase;
  92. else
  93. throw logic_error("unbound Handle");
  94. }
  95. //重载解引用操作符
  96. const Base &operator*() const
  97. {
  98. if(pBase)
  99. return *pBase;
  100. else
  101. throw logic_error("unbound Handle");
  102. }
  103. // 输出引用计数
  104. void print_use()
  105. {
  106. cout<<pBase->getName()<<" use count "<<*use<<endl;
  107. }
  108. private:
  109. //指向引用计数
  110. int *use;
  111. //指向基类的指针,也可以用来指向派生类
  112. Base *pBase;
  113. void des_use()
  114. {
  115. if(--*use == 0)
  116. {
  117. cout<<pBase->getName()<<" delete\n";
  118. delete pBase;
  119. delete use;
  120. }
  121. }
  122. };
  123.  
  124. int main()
  125. {
  126. Handle h1(Base(2,"Base"));
  127. h1.print_use();
  128. cout<<"Base compute:"<<(*h1).compute()<<endl;
  129. Handle h2(h1);
  130. h2.print_use();
  131. cout<<"Base compute:"<<h2->compute()<<endl;
  132. cout<<"*------------------------------------------*\n";
  133. Handle h3(Derived(3, "derive", 3));
  134. h1 = h3;
  135. h1.print_use();
  136. cout<<"Derive compute:"<<(*h1).compute()<<endl;
  137. return 0;
  138. }

二、句柄类

句柄类Handle 有3个构造函数:默认构造函数,复制构造函数,和接收基类Base对象的构造函数。第2个构造函数复制Base类对象保证只有句柄对象

存在副本就存在。其中第2个构造函数的要难。我们希望句柄的用户创建自己的对象,在这些对象上关联句柄。构造函数将分配适当类型的新对象并将

形参复制到新分配的对象中,这样句柄类Handle将拥有对象并 能保证 在关联到该对象的最后一个Handle 对象消失之前不会删除该对象。

复制未知类型。 句柄类经常需要在不知道对象确切类型时分配已知对象的新副本,解决这个问题的通用方法是定义虚操作进行复制,通常命名为clone。

如上面代码中,为了支持句柄类,需要从基类开始,从继承层次的每个类中增加clone,并且基类必须把这个函数定义为虚函数,每个类必须重新定义

这个虚函数,因为函数的存在是为了生成类对象的新副本,所以定义返回类型为类本身,如下面的代码。

  1. //clone函数来返回一个自身的副本
  2. virtual Base* clone() const
  3. {
  4. return new Base(*this);
  5. }

返回类本身类型,且为虚函数。

  1. //接受Base对象 item的构造函数
  2. Handle(const Base &item):pBase(item.clone()),use(new int(1)) {}

这个clone函数必须为虚函数,因为句柄类经常需要在不知道对象的确切类型时分配已知对象的新副本,我们知道,c++ 动态绑定只对 虚函数,且在指针或者引用

时才发生。要解决  复制未知类型 问题只能通过定义一个函数其参数是基类的引用,然后调用一个克隆自身的函数,当这个函数为虚函数时就能发生动态绑定,从而

在不知道 对象本身类型时 我们可以 为这个对象分配一个新副本 并指向它。

整个程序代码运行效果如下:

  1. int main()
  2. {
  3. Handle h1(Base(2,"Base"));
  4. h1.print_use();
  5. cout<<"Base compute:"<<(*h1).compute()<<endl;
  6. Handle h2(h1);
  7. h2.print_use();
  8. cout<<"Base compute:"<<h2->compute()<<endl;
  9. cout<<"*------------------------------------------*\n";
  10. Handle h3(Derived(3, "derive", 3));
  11. h1 = h3;
  12. h1.print_use();
  13. cout<<"Derive compute:"<<(*h1).compute()<<endl;
  14. return 0;
  15. }

从main函数里我们可以看出,句柄类Handle 首先构造一个存储基类对象的句柄,然后再调用3创建新对象,这个句柄依然指向基类对象,所以引用计数为2.

然后再构造句柄h3 其构造对象为派生类对象,再把h2 赋值给h1,此时h1句柄 指向了派生了对象的句柄。

我们可以利用 STL 的容器 保存因继承关联的对象,就是利用上面的句柄类。具体不在详述。

c++ 容器、继承层次、句柄类的更多相关文章

  1. C++ Primer 学习笔记_72_面向对象编程 --句柄类与继承[续]

    面向对象编程 --句柄类与继承[续] 三.句柄的使用 使用Sales_item对象能够更easy地编写书店应用程序.代码将不必管理Item_base对象的指针,但仍然能够获得通过Sales_item对 ...

  2. OOP3(继承中的类作用域/构造函数与拷贝控制/继承与容器)

    当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内.如果一个名字在派生类的作用域内无法正确解析,则编译器将继续在外层的基类作用域中寻找该名字的定义 在编译时进行名字查找: 一个对象.引用或指针的 ...

  3. C++ 句柄类

    一.容器与继承 在容器中保存有继承关系的对象时,如果定义成保存基类对象,则派生类将被切割,如果定义成保存派生类对象,则保存基类对象又成问题(基类对象将被强制转换成派生类对象,而派生类中定义的成员未被初 ...

  4. code of C/C++(3) - 从 《Accelerated C++》源码学习句柄类

    0  C++中多态的概念 多态是指通过基类的指针或者引用,利用虚函数机制,在运行时确定对象的类型,并且确定程序的编程策略,这是OOP思想的核心之一.多态使得一个对象具有多个对象的属性.class Co ...

  5. 动态绑定、阻止继承,final类和方法

    1.编译器查看对象的声明类型和方法名.当调用 x.f(param); 且隐式参数x生命为C类对象.这时候可能有多个名字都叫f,但是参数类型不一样的方法.编译器会一一列举C类中名为f的方法和其超类中访问 ...

  6. C++中的句柄类

    初次在<C++ Primer>看到句柄,不是特别理解.在搜索相关资料后,终于有了点头绪. 首先明白句柄要解决什么问题.参考文章<C++ 沉思录>阅读笔记——代理类 场景: 我们 ...

  7. C/C++ 多继承{虚基类,虚继承,构造顺序,析构顺序}

    C/C++:一个基类继承和多个基类继承的区别 1.对多个基类继承会出现类之间嵌套时出现的同名问题,如果同名变量或者函数出现不在同一层次,则底层派生隐藏外层比如继承基类的同名变量和函数,不会出现二义性, ...

  8. python多继承(新式类)一

    最近在学习python的多重继承. 先来了解下多重继承的概念,所谓多重继承,是指python的类可以有两个以上父类,也即有类A,类B,类C,C同时继承类A与类B,此时C中可以使用A与B中的属性与方法. ...

  9. servlet、filter、listener继承的基类和获得作用域的方式

    一.servlet: 1.servlet属于j2ee的组件,构建servlet的web project不需要导入项目框架jar包 2.servlet的体系结构:  在j2ee API中,提供给serv ...

随机推荐

  1. 转: mysql create view 创建视图

    以下的文章主要是对MySQL视图的描述,其中包括MySQ视图L概述,以及创建MySQL视图-create view与修改MySQL视图--alter view等相关内容的具体描述,以下就是文章的具体内 ...

  2. WPF:在XmlDataProvider上使用主-从绑定(Master-Detail Binding)

    原文 http://www.cnblogs.com/mgen/archive/2011/06/19/2084553.html 示例程序: 如上程序截图,一目了然典型的主从模式绑定应用,如果里面的数据不 ...

  3. 【转】《分享一下我研究SQLSERVER以来收集的笔记》未整理

    分享一下我研究SQLSERVER以来收集的笔记 http://www.cnblogs.com/lyhabc/archive/2013/07/27/3219117.html

  4. FastCGI | FastCGI -

    FastCGI | FastCGI - FastCGI About FastCGI FastCGI is simple because it is actually CGI with only a f ...

  5. ExpandableListView(三)只展开一个group,没有child不展开group

    本文是自己在实践中,发现的问题. 有时候想让界面更加的人性化,就要实现很多的效果,比如只展开一个group,在点击下个group的同时,关闭之前的group 在一个ExpandableListView ...

  6. 第七届河南省赛A.物资调度(dfs)

    10401: A.物资调度 Time Limit: 2 Sec  Memory Limit: 128 MB Submit: 95  Solved: 54 [Submit][Status][Web Bo ...

  7. 新浪微博开放平台开发-android客户端(1)【转】

    http://www.cnblogs.com/virusswb/archive/2011/08/05/2128941.html 最近不是太忙,花了一些时间学习android的应用开发.经过两个星期的学 ...

  8. sublime编辑器怎样高速输入PHP头部版本号声明

    Sublime 菜单条->Tools→New Snippet→得到例如以下图内容: 输入下面内容: <snippet> <content><![CDATA[ < ...

  9. js动画学习(五)

    九.多属性同时运动 前面的例子都是每个属性单独运动,如果想要多属性同时运动怎么办?比如,我想要一个div的onmouseover事件中宽和高同时变化.下面这个函数是单独变宽: window.onloa ...

  10. Java 网络编程(三) 创建和使用URL访问网络上的资源

    链接地址:http://www.cnblogs.com/mengdd/archive/2013/03/09/2951877.html 创建和使用URL访问网络上的资源 URL(Uniform Reso ...