条款32:

1.公有继承是一种is-a关系

2.任何函数如果期望获得一个类型为基类(pointer-to基类或reference-to基类)的实参,都也愿意接受一个继承类对象。这点只对public继承成立。

3.如果解决public继承中“不是所有的鸟都会飞”问题?

4.某些可施行于矩阵身上的事情却不可以施行于正方形身上(故不可以public继承)

条款33:

1.内层作用域的名称会掩盖外层作用域的名称(编译器先在local作用域查找,找不到再去其他作用域)

2.继承类的作用域被嵌套在基类作用域内,所以继承类内的名称会覆盖基类名称。

3.继承类内的函数名与基类内函数名相同,类型不同的重载函数,继承类内的函数会遮掩这个函数。

4.如果继承基类并且加上重载函数,又希望重新定义或覆写其中一部分,继承类遮掩了基类内的同名重载函数,可以使用using声明式达成目标。(using声明式放在public区域:bass class内的public名称在publicly dereved class内应该是public)

5.如果是私有继承,using声明式派不上用场,using声明式会令继承而来的某给定名称之所有同名函数在derived class中都可见。

6.如果不想继承base class所有函数,private derived可以运用转交函数来遮掩base class带参数的重载函数,不需要使用using声明式。

class Base{
public:virtual void mf1() = 0;virtual void mf1(int);//...
};class Derived :private Base{
public:virtual void mf1(){Base::mf1();}//...
};

条款34

1.拥有纯虚函数的基类是一个抽象类,只能被继承,不能创建基类实体,只能创建derived class的实体。

2.public继承由两部分组成,函数接口继承和函数实现继承。其中函数的接口就是声明。

3.纯虚函数就是只有声明,没有实现,是接口继承。任何“继承了”他们的具象class必须重新声明,而且他们在抽象class中通常没有定义。声明一个纯虚函数是为了让derived class只继承函数接口。

4.可以为纯虚函数提供定义,但调用它的唯一途径是“调用时明确指出其class名称”。

Shape* ps = new Shape; //错误,Shape是抽象类
Shape* ps1 = new Rectangle;  //OK
ps1->draw();           //调用Rectangle::draw
Shape* ps2 = new Ellipse;    //OK
ps2->draw();           //调用Ellipse::draw
ps1->Shape::draw();    //调用Shape::draw
ps2->Shape::draw();    //调用Shape::draw

5.声明简朴的impure virtual函数的目的,是让继承类继承该函数的接口和缺省实现。即非纯虚函数能同时指定函数声明和函数缺省行为。

6.允许impure virtual函数同时指定函数声明和函数缺省行为有可能造成危险。(某些不需要该缺省行为的继承类也拥有了)

7.解决6中问题的方法是切断“virtual函数接口”和其“缺省实现”之间的连接。

class Airplane{
public:virtual void fly(const Airport& destination) = 0;//...
protected://因为是Airplane及其继承类的实现细目。乘客不需要知道怎么飞void defaultFly(const Airport& destination);
};void Airplace::defaultFly(const Airport& destination)
{//缺省行为,将飞机飞至指定目的地
}

这次纯虚函数只提供接口,缺省行为由独立函数defaultFly提供。若想使用缺省实现,可以在其fly函数中对defaultFly做一个inline调用。

这样继承类就不可能意外继承不正确的fly实现代码了。

8.上一条中过度雷同的杉树名称可能引起class命名空间污染问题。下面利用“pure virtual函数必须在derived class中重新声明,但他们也可以拥有自己的实现”这一事实。

class Airplane{
public:virtual void fly(const Airport& destination) = 0;//...
};void Airplace::fly(const Airport& destination)
{//缺省行为,将飞机飞至指定目的地
}class ModelA::public Airplane{
public:virtual void fly(const Airport& destination){Airplane::fly(destination);}
};class ModelB::public Airplane{
public:virtual void fly(const Airport& destination);
};void ModelB::fly(const Airport& destination)
{//将B型飞机飞至指定目的地
}

9.声明non-virtual函数的目的是为了令derived classed继承函数的接口以及一份强制实现。

条款35:

见条款35(考虑virtual函数以外的其他选择)之Template Method模式和Strategy模式

条款36:

1.non-virtual函数是静态绑定的,virtual函数是动态绑定的。

#include <iostream>
using namespace std;class B{
public:void mf(){ cout << "Base" << endl; }//...
};class D :public B{
public:void mf(){ cout << "Derived" << endl; }
};int main()
{D x;B* pB = &x;D* pD = &x;pB->mf();pD->mf();return 0;
}

输出是:

Base
Derived

原因是pB被声明为一个pointer-to-B,通过pB调用的non-virtual函数永远是B所定义的版本,即使pB指向一个类型为B派生的类的对象。

如果mf是个virtual函数,不论是通过pB或pD调用mf,都会导致调用D::mf,因为pB和pD真正指的都是一个类型为D的对象。

#include <iostream>
using namespace std;class B{
public:virtual void mf(){ cout << "Base" << endl; }//...
};class D :public B{
public:void mf(){ cout << "Derived" << endl; }
};int main()
{D x;B* pB = &x;D* pD = &x;pB->mf();pD->mf();return 0;
}

输出是:

Derived
Derived

2.任何一个对象可能表现出B或D的行为,决定因素在于“指向该对象的指针”当初的声明类型。

条款37:

1.virtual函数是函数动态绑定,而缺省参数值却是参数静态绑定,非virtual函数是函数静态绑定。(静态绑定又名前期绑定,动态绑定又名后期绑定)

2.静态类型是它在程序中被声明时所采用的类型,所谓动态类型是指“目前所指对象的类型”。

#include <iostream>
using namespace std;class Shape{
public:enum ShapeColor{ Red, Green };virtual void draw(ShapeColor color = Green) const = 0{cout << "noColor" << endl;}
};class Rectangle :public Shape{
public:virtual void draw(ShapeColor color = Red) const{cout << (color==Green?"Green":"Red") << endl;}
};class Circle :public Shape{
public:virtual void draw(ShapeColor color) const{cout << (color == Green ? "Green" : "Red") << endl;}
};int main()
{Shape* pc=new Circle;pc->draw();                    //使用Shape的缺省参数GreenShape* pr = new Rectangle;pr->draw();                    //使用Shape的缺省参数Greenpr->draw(Shape::Red);          //不使用缺省参数Rectangle* r = new Rectangle;r->draw();                     //使用Rectangle的缺省参数Redreturn 0;
}

输出是:

Green
Green
Red
Red

注意:缺省情况下调用的函数是继承类的,但是默认参数是基类提供的。

3.缺省参数值是静态绑定的,这样编译器就可以在编译期决定,这样可以提高运行期效率。

4.使用条款35的NVI手法可以避免这种情况:令base class 内的一个public non-virtual函数调用private virtual函数,后者可以被derived classes重新定义。这里我们让non-virtual函数指定缺省参数,而private virtual函数负责真正的工作。

class Shape{
public:enum ShapeColor{Red,Green,Blue};void draw(ShapeColor color = Red) const{doDraw(color);}
private:virtual void doDraw(ShapeColor color) const;
};class Rectangle :public Shape{
public://...
private:virtual void doDraw(ShapeColor color) const;
};

条款38:

1.复合关系是指某种对象内含有它中类型的对象,复合意味着has-a或is-implemented-in-terms-of。

2.人、汽车、一张视频画面等属于应用域,缓冲区、互斥器、查找树等属于实现域。当复合发生在应用域内的对象之间表现出has-a关系;当它发生在实现域内,则是表现is-implemented-in-terms-of的关系。

注意:STL中vector与list的关系就是后者。

3.如何根据list实现set。

条款39:

1.protected成员:对外界来说,行为与私有成员相似,对派生类来说,行为与公有成员相似。

2.如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象。

3.由private base class继承而来的所有成员,在dervied class中都会变成private属性。纵使他们在基类中原本是protected或public属性。

4.private继承意味着implemented-in-terms-of,private继承只是一种实现技术,private base class的每样东西对继承类来说都只是实现枝节而已。

5.基类的virtual函数经过private继承后,在dervied class中都会变成private属性,如果重定义在dervied class的public下,则会提供public接口,仍然可以运行,但是不要这样做,这样会让客户端以为他们可以调用它。

#include <iostream>
using namespace std;class Timer{
public:explicit Timer(int tickFrequency){}virtual void onTick() const{ cout << "Timer" << endl; }//...
};class Widget :private Timer{
public:Widget() :Timer(1){}/*void testOnTick(){onTick();}*/virtual void onTick() const{ cout << "Widget" << endl; }
};int main()
{Widget test;test.onTick();return 0;
}

输出:

Widget

6.可以用复合来代替private继承,采用嵌套class,阻止继承类重新定义onTick()。

class Widget{
private:class WidgetTimer :public Timer{public:virtual void onTick() const;//...};WidgetTimer timer;//...
};

7.上面那种方法可以模拟Java和C#的“阻止derived class重新定义virtual函数”。

8.如果是继承,则必须包含头文件,如果是复合,则只需前置声明。所以使用复合可以降低编译依存性。

9.private主要用于“当一个意欲成为继承类者想访问一个意欲成为基类的protected成分,或为了重新定义一个或多个virtual函数。”

10.当所处理的class不带任何数据时(没有non-static成员变量,没有虚函数,也没有virtual base class),选择private继承而不是“继承加复合”,可使空间最优化。

#include <iostream>
using namespace std;class Empty{};class HoldsAnInt1{
private:int x;Empty e;
};class HoldsAnInt2:private Empty{
private:int x;
};int main()
{cout << sizeof(Empty) << endl;cout << sizeof(int) << endl;cout << sizeof(HoldsAnInt1) << endl;     //大于sizeof(int)cout << sizeof(HoldsAnInt1) << endl;     //大多数编译器等于sizeof(int)return 0;
}

输出结果:

1
4
8
8(注:我的VS2013输出8,大多编译器输出4)

这就是所谓的空白基类最优化在(emptybase optimization-EBO 或 empty base classopimization-EBCO)。在空基类被继承后由于没有任何数据成员,所以子类优化掉基类所占的1byte
11.上面空表内占的内存为1字节,因为面对“大小为零的独立对象”,通常C++官方勒令默默安插一个char到空对象内。

12.STL有许多技术用途的empty classes,其中内含有用的成员(通常是typedefs),包括基类unary_function和binary_function,这些是“用户自定义的函数对象”,通常会被继承的classes。

条款40:

1.如何进行多重继承?

#include <iostream>
using namespace std;class BorrowableItem{
public:void checkOut(){ cout << "BorrowableItem" << endl; }//...
};class ElectronicGadget{
private: //虽然是private,但两个checkOut有相同的匹配程度bool checkOut(){cout << "ElectronicGadget" << endl;return 1;}//...
};class MP3Player :public BorrowableItem, public ElectronicGadget  //多重继承
{};int main()
{MP3Player mp;mp.checkOut();   //error:歧义,调用哪个checkOutreturn 0;
}

注:基类的private函数经过public继承后,对继承类来说,仍然是private。

2.如果程序从一个以上的base classes继承相同的名称(如函数、typedef等等),会导致歧义,如何解决?

可以在调用时明确调用那个checkOut函数。

mp.BorrowableItem::checkOut();   //OK
mp.ElectronicGadget::checkOut(); //error:cannot access private member

3.钻石型多重继承:有一个继承体系而其中某个base class和某个derived class之间有一条以上的相通线路。

class File{};
class InputFile :public File{};
class OutputFile :public File{};
class IOFile :public InputFile, public OutputFile
{};

C++的缺省做法是base class内的成员变量经由每一条路径被复制,IOFile从其每一个base class继承一份,所以其对象内应该有两份fileName成员变量。

4.如果不想有上述重复情况,必须令那个带有此数据(File)成为一个virtual base class。这样,必须令直接继承它(File)的classes(InputFile和OutputFile)采用“virtual继承”

class File{};
class InputFile :virtual public File{};
class OutputFile :virtual public File{};
class IOFile :public InputFile, public OutputFile
{};

5.C++标准程序库内含一个多重继承体系,结构和4相似,只不过class是class template,名称分别是basic_ios,basic_istream,basic_ostream和basic_iostream。
6.使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们体积大,访问virtual base classes的成员变量时,也比访问non-virtual base classes的成员变量速度慢。(使用virtual base classes,经可能避免在其中放置数据)

7. 下面补充一个关于virtual函数和多重继承的隐晦错误
#include <iostream>
using namespace std;class Base1{
public:virtual void foo1() {};
};class Base2{
public:virtual void foo2() {};
};class MI : public Base1, public Base2{
public:virtual void foo1 () {cout << "MI::foo1" << endl;}virtual void foo2 () {cout << "MI::foo2" << endl;}
};int main(){MI oMI;Base1* pB1 =  &oMI;pB1->foo1();Base2* pB2 = (Base2*)(pB1); // 指针强行转换,没有偏移pB2->foo2();pB2 = dynamic_cast<Base2*>(pB1); // 指针动态转换,dynamic_cast帮你偏移pB2->foo2();return 0;
}

你会认为屏幕上会输出什么?是下面的结果吗?

MI::foo1

MI::foo2

MI::foo2

这样认为没有什么不对的,因为C++的多态性保证用父类指针可以正确的找到子类实现,并调用。所以会有上面的输出。

但是,现实却不是这样,下面是真实的输出:

(以上实现在VC 2005和Linux Gcc 4.1.2效果一致)

为什么

为什么会出现上面的情况呢,上面代码中的注释部分也许解释了,这里再来详细的来分析一下。

首先,C++使用一种称之为vtable(google “vtable” for more details)的东西实现virtual函数多态调用。vtable每个类中都有一个,该类的所有对象公用,由编译器帮你生成,只要有virtual函数的类,均会有vtable。在继承过程中,由于类Base1和类Base2都有vtable,所以类MI继承了两个vtable。简单的分析一下对象oMI内存结构,如下:

0 vtable_address_for_Base1 –> [MI::foo1, NULL]

4 vtable_address_for_Base2 –> [MI::foo2, NULL]

其实很简单,就两个vtable的指针,0和4代表相对地址,指针地址大小为4。

pB1的值为0(pB1 == 0),所以调用“pB1->foo1()”时,可以正确的找到MI::fool这个函数执行。

但是当使用强行转换,将pB1转给pB2,那么实质上pB2的值也是0(pB2 == 0),当调用“pB2->foo2()”时,无法在第一个vtalbe中找到对应的函数,但是却不报错,而是选择执行函数MI::foo1,不知道为什么会有这种行为,但是这种行为却十分恶心,导致结果无法预期的(最后调用的函数会与函数申明的循序有关),不太会引起注意,使得bug十分隐晦。

可以设想,当一个有复杂的业务逻辑的程序,而类似这种函数调用和指针强行转换分布在不同的函数或模块中,可想而知,bug定位十分困难。

当使用动态转换时,也就是“pB2 = dynamic_cast<Base2*>(pB1)”,dynamic_cast函数会根据尖括号中的类型进行指针偏移,所以pB2的值为4(pB2 == 4),这样调用“pB2->foo2()”就会按照期望的方式执行。

结论

上面的现象在单继承中是不会出现的,因为只有一个vtable(子类的virtual函数会自动追加到第一个父类的vtable的结尾)。所以不会出现上面的现象,而多重继承却出现了上面的想象,所以需要注意以下两点:

1)多重继承需要慎用

2)类型转换尽量采用c++内置的类型转换函数,而不要强行转换

Conclusion for Inheritance and Object Oriented Design相关推荐

  1. Object oriented Design

    OO Concepts – Object model (abstraction, encapsulation, modularity, hierarchy); data abstraction; in ...

  2. Java SE 008 理解面向对象程序设计 (Inside Object Oriented Programming)

    Java SE 008 理解面向对象程序设计 (Inside Object Oriented Programming) 前言:此笔记为圣思园张龙老师讲述的java视频课程笔记,自己看视频学习时记录的, ...

  3. OO开发思想:面向对象的开发方法(Object oriented,OO)

    面向对象的开发方法(Object oriented,OO)认为是好文章吧,拿来分享一下(转载) 面向对象的开发方法(Object oriented,OO) 从事软件开发的工程 师们常常有这样 的体会: ...

  4. 面向对象编程(Object Oriented Programming)概念总结及延伸(一)

    1.介绍 笔者的梦想是成为一个架构师,但是要成为一个合格的架构师是相当不易的,它既需要丰富的项目经验也需要不断地吸取新的知识,而且在这过程中我们也要不断巩固基础知识.我也注意到了,现在主流的文章大都集 ...

  5. Java基础篇--面向对象(Object Oriented)

    Java基础篇--面向对象(Object Oriented) 1. 面向对象概念 1.1 什么事面向对象 1.2 理解面向对象 1.3 面向对象的特点 1.4 面向对象开发设计特征 2. 类与对象 2 ...

  6. python Object Oriented Programming

    python 知识点整理(五) 本文只是对python部分知识点进行学习和整理 本篇主要是针对python的Object Oriented Programming的总结 本文目录 python 知识点 ...

  7. Java OOP(Object Oriented Programming)个人理解及总结

    面向对象编程(Object Oriented Programming,OOP,面向对象程序设计) 其三大特征:封装,继承,多态: 封装:解决数据的安全问题. 继承:解决代码的重用问题. 多态:解决程序 ...

  8. Python编程基础:第三十九节 面向对象编程Object Oriented Programming

    第三十九节 面向对象编程Object Oriented Programming 前言 实践 前言 到目前为止我们都是函数式编程,也即将每一个功能块写为一个函数.其实还有一种更常用的编程方式被称为面向对 ...

  9. Coursera课程Python for everyone:Quiz: Object Oriented Programming

    Object Oriented Programming 11 试题 1. Which came first, the instance or the class? instance class fun ...

最新文章

  1. Java项目:潜艇大战项目(java+swing)
  2. .NET中的枚举(Enum)
  3. Notepad++ JSON关键字自动提示
  4. 【机器学习】机器学习用到的常用术语
  5. Visual C++ MFC/ATL开发-提高篇
  6. android sqlite使用之模糊查询数据库数据的三种方式
  7. Redis学习---(10)Redis 集合(Set)
  8. SAP License:HANA在线日志被误删后如何恢复?
  9. android怎么查看方法被谁调用,Android中查看布局文件中的控件(view,id)在哪里被调用(使用)...
  10. Appium+python自动化(十一)- 元素定位秘籍助你打通任督二脉 - 下卷(超详解)...
  11. wininet InternetOpen\InternetOpenUrl\InternetReadFile 等
  12. 日本企业给我们的启示
  13. 冰点还原标准版-中文版(全面支持Windows 7)7.0.020.3172(最新版)下载与注册
  14. Re:天选2之找不到WLAN网络
  15. Electron播放 RTMP流 实现
  16. 添加视频字幕后期制作Premiere Pro 2022中文
  17. Adam Harley的卷积神经网络3D视觉化模型
  18. 推荐系统——矩阵分解
  19. 关于正向级数收敛而它的平方也收敛的证明
  20. Ajax与分页的实现

热门文章

  1. unsplash 图片版权
  2. Linux下搭建FTP服务器教程
  3. 茴字的四种写法——浅谈移动前端适配
  4. 插件中的chalk的用法
  5. 应用程序无法正常启动(0xc000007b) 问题解决
  6. matlab norm函数使用_MATLAB 中NORM运用
  7. python的数学建模库_数学建模库
  8. Introduce·传播学核心期刊推荐之《现代传播(中国传媒大学学报)》
  9. 【二次开发】如何使用C#进行CATIA二次开发
  10. 调试助手与康耐视智能相机Insight以太网通讯