关于OOP

博客地址:http://www.cnblogs.com/ronny 转载请注明出处!

1,继承可以是单一继承或多重继承,每一个继承连接可以是public、protected或private,也可以是virtual或non-virtual。

2,成员函数的各个选项:virtual或non-virtual或pure-virtual。

3,成员函数和其他语言特性的交互影响:缺省参数值与virtual函数有什么交互影响?继承如何影响C++的名称查找规则?设计选项有如些?如果class的行为需要修改,virtual函数是最佳选择吗?

4,public继承意味着“is-a”。

5,virtual函数意味着“接口必须被继承”,non-virtual意味着“接口和实现都必须被继承”。

条款32:确定你的public继承塑模出is-a关系

“public继承”意味着is-a。适用于base classes身上的每一件事情一定也适合于derived class身上,因为每个derived classes对象也都是一个base classes对象。

上面这种关系听起来颇为简单,但有时候你的直觉可能会误导你,比如,企鹅是一种鸟,这是事实。鸟可以飞,这也是事实,但是如果在用类描述时把企鹅定义为鸟的public继承则会出问题,比如给基类定义fly函数。

条款33:避免遮掩继承而来的名称

derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如些。

  1. // 博客地址:http://www.cnblogs.com/ronny/ 转载请注明出处!
  2. class Base
  3. {
  4. private:
  5. int x;
  6. public:
  7. virtual void mf1() = 0;
  8. virtual void mf1(int);
  9. virtual void mf2();
  10. void mf3();
  11. void mf3(double);
  12. };
  13. class Derived :public Base
  14. {
  15. public:
  16. virtual void mf1(); //屏蔽了Base里的mf1(int)
  17. void mf3(); // 屏蔽了Base里的mf3
  18. void mf4();
  19. };
  1. Derived d;
  2. int x;
  3. d.mf1(); //ok,调用Derived::mf1
  4. d.mf1(x); //error,因为Derived::mf1遮掩Base=::mf1
  5. d.mf2(); //ok,调用Base::mf2
  6. d.mf3(); //ok,调用Derived::mf3
  7. d.mf3(x); // error,因为Derived::mf3遮掩了Base::mf3

Derived内的函数遮掩了Base内所有的同名函数。当然实现中如果是一种public继承,则是一定需要同理继承重载函数的,因为必须符合is-a关系。

我们可以用using声明式达成目标:

  1. class Derived :public Base
  2. {
  3. public:
  4. using Base::mf1;
  5. using Base::mf3;
  6. virtual void mf1();
  7. void mf3();
  8. void mf4();
  9. };
  1. Derived d;
  2. int x;
  3. d.mf1(); //ok,仍然调用Derived::mf1
  4. d.mf1(x); //现在没有问题了
  5. d.mf2(); //ok,调用Base::mf2
  6. d.mf3(); //ok,仍然调用Derived::mf3
  7. d.mf3(x); //没有问题了

我们还可以使用交换函数来完成这个功能:有时候我们并不想继承base classes的所有函数。

  1. class Derived :private Base
  2. {
  3. public:
  4. virtual void mf1() // 转交函数
  5. {
  6. Base::mf1();
  7. }
  8. };

条款34:区分接口继承和实现继承

接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。

pure virtual函数只具体指定接口继承

简朴的(非纯)impure virtual函数具体指定接口继承及缺省实现继承。

non-virtual函数具体指定接口继承以及强制性实现继承

条款35:考虑virtual函数以外的其他选择

这条款谈了两种设计模式,鼓励我们多思考的。

以游戏中的人物设计继承体系为例子,不同的人物有不同的计算健康指数的方法,就叫healthValue函数吧,很自然的就会想到设计一个基类,把healthValue函数设计为virtual的用于继承。

此条款提供了两种不同的思路,用于virtual替代方案:

1. Template Method模式,由Non-Virtual Interface手法实现。

具体到以上的例子就是大概如下:

  1. class GameCharacter {
  2. public:
  3. int healthValue() const {
  4. // ...
  5. int retVal = doHealthValue();
  6. // ...
  7. return retVal;
  8. }
  9. private:
  10. virtual int doHealthValue() const {
  11. }
  12. };

 

其实最先提的把healthValue设计为virtual的方法也是Template Method模式,而这里就是保留healthValue为public,而实现为private virtual(当然这里是可以protected的),这样的好处在于其中的前后“...”(省略号),这部分可以进行一些类似检查、调整的操作,保证doHealthValue()在一个适当的场景下调用。而且子类也可以继承实现private virtual成员函数。

2. Strategy模式,这一模式令实现方法是个变量,就算是同一个对象在不同的时段也可以有不同的实现方法。但这里都有个约束,就是对私有成员变量的访问限制。

a) Function Pointers实现,此种实现手法的约束是只能是函数,而且形式受函数的签名(参数数量,参数类型,返回类型)的约束。

b) tr1::function实现,摆脱了a)的约束,支持隐式类型转换,还支持函数对象或者是成员函数(通过std::tr1::bind实现)

c) 古典实现,其实就是对象实体是一类,而实现方法是另一类。

条款36:绝对不重新定义继承而来的non-virtual函数

当派生类中重新定义了基类中的non-virtual函数时,如果用指向基类类型的指针(实际指向派生类对象)来访问该non-virtual函数时,访问的是基类对象的non-virtual函数。

造成上面行为的原因是,non-virtual是静态绑定的,区别于virtual函数的动态绑定。

条款37:绝不重新定义继承而来的缺省参数值

virtual函数系动态绑定的,而缺省参数值是静态绑定的。

所谓对象的静态类型:在程序中被声明时被采用的类型。而对象的动态类型是指“目前所指对象的类型”,也就是说,动态类型可以表现出一个对象将会有什么行为。

  1. class Shape
  2. {
  3. public:
  4. enum ShapeColor{ Red, Green, Blue };
  5. virtual void draw(ShapeColor color = Red)const = 0;
  6. };
  7. class Rectangle :public Shape
  8. {
  9. public:
  10. virtual void draw(ShapeColor color = Green)const = 0;
  11. };

上面的代码中,如果我们定义了一个类型为Shape*的指针指向Rectangle。

Shape* ps=new Rectangle;

ps->draw();

我们的实际愿望可能是希望让draw的默认参数为Green,但是实际上确是Red,因为默认参数是静态绑定的。

也许你可能会想让派生类的中virtual函数和base类中保持一致。但是下面的代码显然也是不可行的。

  1. class Shape
  2. {
  3. public:
  4. enum ShapeColor{ Red, Green, Blue };
  5. virtual void draw(ShapeColor color = Red)const = 0;
  6. };
  7. class Rectangle :public Shape
  8. {
  9. public:
  10. virtual void draw(ShapeColor color = Red)const = 0;
  11. };

上面不仅会产生代码重复,而且带来了相依性(如果Shape内的缺省参数变了,所有派生类的virtual函数缺省值都要修改)。

聪明的做法是考虑替代设计,如条款35中的一些virutal函数的替代设计,其中之一是NVI手法,令base class内的一个public non-virtual函数调用private virtual函数。

  1. class Shape
  2. {
  3. public:
  4. enum ShapeColor{ Red, Green, Blue };
  5. virtual void draw(ShapeColor color = Red)const
  6. {
  7. doDraw(color);
  8. }
  9. private:
  10. virtual void doDraw(ShapeColor color)const = 0;
  11. };
  12. class Rectangle :public Shape
  13. {
  14. private:
  15. virtual void doDraw(ShapeColor color)const;
  16. };

条款38:通过复合塑模出has-a或“根据某物实现出”

复合或包含意味着has-a。如果我们想设计一个自己的set,我们思考后觉得可以用list来实现它,但是如果我把它设计出list的一个派生类,就会有问题,因为父类的所有行为在派生类都是被允许的,而list允许元素重复,而set则显然不行,所以set与list之间不符合is-a关系,我们可以把list设计为set的一个成员,即包含关系(has-a)。

条款39:明知而审慎地使用private继承

private继承意味着is-implemented-in-terms of(根据某物实现)。它通常比复合的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。

和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。

  1. class Empty{};
  2. class HoldsAnInt
  3. {
  4. private:
  5. int x;
  6. Empty e;
  7. };

这时候你会发现sizeof(HoldsAnInt)>sizeof(int),因为一些内存对齐的要求。

而如果是:

  1. class HoldsAnInt :private Empty{
  2. private:
  3. int x;
  4. };

这时候几乎可以肯定:sizeof(HoldsAnInt)==sizeof(int)。

条款40:明智而审慎地使用多重继承

使用多重继承就要考虑歧义的问题(成员变量或者成员函数的重名)。

最简单的情况的解决方案是显式的调用(诸如item.Base::f()的形式)。

复杂一点的,就可能会出现“钻石型多重继承”,以File为例:

  1. class File { ... }
  2. class InputFile : public File { ... }
  3. class OutputFile : public File { ... }
  4. class IOFile : public InputFile, public OutputFile { ... }

这里的问题是,当File有个filename时,InputFile与OutputFile都是有的,那么IOFile继承后就会复制两次,就有两个filename,这在逻辑上是不合适的。解决方案就是用virtual继承

  1. class File { ... }
  2. class InputFile : virtual public File { ... }
  3. class OutputFile : virtual public File { ... }
  4. class IOFile : public InputFile, public OutputFile { ... }

这样InputFile与OutputFile共享的数据就会在IOFile中只保留一份了。

但是virtual继承并不常用,因为:

1. virtual继承会增加空间与时间的成本。

2. virtual继承会非常复杂(编写成本),因为无论是间接还是直接地继承到的virtual base class都必须承担这些bases的初始化工作,无论是多少层的继承都是。

请记住

多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要。

virtual继承会增加大小、速度、初始化(赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具实用价值的情况。

多重继承的确有正当用途。其中一个情节涉及“public继承某个Interface class”和“private继承某个协助实现的class”的两相组合。

Effective C++笔记:继承与面向对象设计的更多相关文章

  1. Effective C++(20) 继承与面向对象设计

    本文主要参考<Effective C++ 3rd>中的第六章部分章节的内容. 关注的问题集中在继承.派生.virtual函数等.如: virtual? non-virtual? pure ...

  2. Effective C++: 06继承与面向对象设计

    32:确定你的public继承塑模出is-a关系 以C++进行面向对象编程,最重要的一个规则是:public继承表示的是"is-a"(是一种)的关系. 如果令class D以pub ...

  3. Effective C++ 6.继承与面向对象设计

    //条款32:确定你的public继承塑模出is-a关系 // 1.public继承意味着is-a的关系,适用在基类上的方法都能用于派生类上. //条款33:避免遮掩继承而来的名称 // 1.在pub ...

  4. 【Effective C++】继承与面向对象设计

    关于OOP 1,继承可以是单一继承或多重继承,每一个继承连接可以是public.protected或private,也可以是virtual或non-virtual. 2,成员函数的各个选项:virtu ...

  5. EffectiveC++ 第6章 继承与面向对象设计

    我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的"可能比较准确"的「翻译」. Chapter 6 继承与面向对象设计 Inheritance and ...

  6. Effective C++ —— 继承与面向对象设计(六)

    条款32 : 确定你的public继承塑模出is-a关系 以C++进行面向对象编程,最重要的一个规则是:public inheritance(公开继承)意味“is-a”(是一种)的关系.请务必牢记.当 ...

  7. Effective C++笔记(六):继承与面向对象设计

    参考:http://www.cnblogs.com/ronny/p/3756494.html 条款32:确定你的public继承塑模出is-a关系 “public继承”意味着is-a.适用于base ...

  8. Effective C++ -- 继承和面向对象设计

    32.确保你的public继承了模is-a关系 public继承意味着is-a关系(里氏替换原则),一切适用于基类也适用于派生类. 矩形继承正方形问题: 可实施与矩形的操作无法实施与正方形 在编程领域 ...

  9. Effective C++ ——继承与面向对象设计

    条款32:确定你的public继承塑模出is-a关系 以public继承的类,其父类的所有的性质都应该使用与子类,任何需要父类的地方都应该能用子类来代替,任何子类类型的对象也同时是父类的: class ...

随机推荐

  1. MVVM Light 笔记 - snippet

    RelayCommand有8个,看似很多,其实就是几个变化的组合: 1.是否Generic 2. 执行是使用lambda表达式还是method 3.是否有CanExecute 这些都在源代码Snipp ...

  2. Java在dos界面运行java源文件编译成功,但运行虚拟机时出现错误:“找不到或无法加载主类”的问题

    (一)首先检查环境变量配置有没有问题, 1PATH为%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin; 2CLASSSPATH为.;%JAVA_HOME%\lib\dt.jar; ...

  3. c# 通过按钮获取文件夹和打开磁盘文件

    Button控件获取文件夹: FolderBrowserDialog fileDialog = new FolderBrowserDialog(); if (fileDialog.ShowDialog ...

  4. CollisionFlags

    CollisionFlags是CharactorController的返回值,表示碰撞的信息 Values: None Sides Above Below function Update () { v ...

  5. Mybatis简介、环境搭建和详解

    简介: 1.Mybatis  开源免费框架,原名叫iBatis,2010在google code,2013年迁移到github 2.作用: 数据访问层框架 2.1  底层是对JDBC的封装 3.myb ...

  6. 使用Hadoop API 解压缩 HDFS文件

    接上篇:使用Hadoop API 压缩HDFS文件 压缩完了,当然需要解压缩了. 直接上代码: private static void getFile(String filePath) throws ...

  7. 674. Longest Continuous Increasing Subsequence

    static int wing=[]() { std::ios::sync_with_stdio(false); cin.tie(NULL); ; }(); class Solution { publ ...

  8. 2018.10.26 NOIP模拟 瓶子 (dp/贪心)

    传送门 正解是dp并不想去想了. 自己yy了一个贪心拿了95pts95pts95pts,唯一没过的点还只有一个地方错了,面向数据变成之后过啦! 所以我讲讲如何贪心. 考虑到最后都只会合并成一种颜色,所 ...

  9. linux 下安装安装mysql 5.6. 5.7

    linux版本:CentOS7 64位 5.7.20 安装请看 他人博客 我已经安装成功了 https://www.cnblogs.com/cz-xjw/p/8006904.html 5.6安装 前提 ...

  10. R入门(一)

    简单的算术操作和向量运算 向量赋值:函数c( ),参数可以是一个或多个数,也可以是向量 赋值符号‘<-’ 向量运算:exp(),log(),sin(),tan(),sqrt(),max(),mi ...