43.明智地使用多继承。

多继承带来了极大的复杂性。最主要的一条就是二义性。

当派生类为多继承时,其多个基类有同名的成员时,就会出现二义性。通常要明白其使用哪个成员的。显式地限制修饰成员不仅非常笨拙,并且会带来限制。当显式地用一个类名来修饰一个虚函数时,函数就会被固定,而不再具有虚拟的特性。对于虚函数,若两个基类拥有一个同名同參的虚函数,当派生类没有又一次定义虚函数时(能够仅仅声明),直接调用这个同名函数会出二义性错误,须要指明其类。而当派生类中又一次定义了这个函数,这是不可能的,由于一个类仅仅同意有唯一同參同名的函数(事实上不正确,函数体声不声明const,还是不同的,一个用于正常对象,一个用于const对象)。而对于派生类中又一次定义虚函数,事实上是在派生类中又一次创建了一个函数,在这里要又一次定义两个虚函数是不行的,由于重定义一个函数就是在创建一个函数,而一个函数里不能有两个同名同參的函数。

当不改动虚函数时,仅仅要用指明基类的方式去调用基类的函数就可以,当须要又一次定义一个虚函数时,即对于派生类仅仅保留一个虚函数时,不用关心其保留哪个虚函数,仅仅要正常的又一次声明定义就可以。

而当须要又一次定义多个虚函数,且派生类要用到这多个虚函数时,一种所谓的巧妙的方法解决二义性,就是在存在二义性的两个基类下再进行派生,在这两个派生类中给其定义各自的新名字,而函数体为内联的调用基类的函数。而多重继承的派生类多重继承与这两个中间类,就将两个原本冲突的基类函数变成了两个不冲突的基类函数。只为又一次定义一个虚函数,而不得不引入新的类。

class A{
public:
virtual void fun(){cout<<"A"<<endl;}
};
class B{
public:
virtual void fun(){cout<<"B"<<endl;}
};
class AuxA:public A{
public:
virtual void Afun() = 0;
virtual void fun(){return Afun();}
};
class AuxB:public B{
public:
virtual void Bfun() = 0;
virtual void fun(){return Bfun();}
}; class C:public AuxA,public AuxB{
public:
virtual void Afun(){cout<<"A in C"<<endl;}
virtual void Bfun(){cout<<"B in C"<<endl;} };
class D:public A,public B{ };
int main(){
D* d = new D();//对于正常的情况,
A* aaa =d;//当其使用基类的指针时,
aaa->fun();//就会调用基类的虚函数,而不会发生冲突
B* bbb = d;//全然没有体现到多态
bbb->fun();
//d->fun();//而直接调用会二义性。
C* c =new C();
//c->fun();//改名后原来的这个函数还是二义性的。
A* aa = c;
aa->fun();//输出 A in C
B* bb = c;//输出 B in C
bb->fun();
AuxA *a = c;
AuxB * b = c;
a->fun();//输出 A in C
b->fun();//输出 B in C

这样就同个改名而在派生类中又一次定义了两个基类的同名函数。

而除了二义性,还常常碰到的问题就是菱形继承,也就是一个基类被继承多次,可是否应该保存多个拷贝的问题。一般来说都是仅仅拥有一个这种基类,即将其声明为虚基类。

但这样也是有问题的,首先程序开发这设计一个基类A派生了多个基类BC,可是在其定义BC时无法知道以后是否有人会多继承BC,而后人想要改动BC的定义使其虚继承于类A又是非常难做到的,一般ABC都是仅仅读的库函数,而D由库的用户开发。还有一方面,假设A声明为BC的虚基类,这在大部分情况下会给用户带来空间和时间上的额外消耗。

而对于虚基类,若A为非虚基类,则D的对象在内存中的分配通常占用连续的内存单元,而若A为虚基类,会包括一个指针指向虚基类数据成员的函数单元。这里对于A派生BC,而D继承与B和C,则D的内存中有两个A,而假设是虚基类,D中有两个指向A的指针。

所以考虑这些,进行高效的类设计时,若涉及到MI 多继承,作为库的设计者就要具有超凡的远见。

向虚基类传递构造函数參数。对于单继承中,派生类在成员初始化列表中对基类传递參数,因为是单继承的,这些參数能够逐层的向上传递。但虚基类的构造函数就不同了,它的參数由继承结构中最底层的派生类的成员初始化列表来指定,假设有新类添加到继承结构中,可能要改动运行初始化的类。避免这个问题的办法是消除对虚基类传递构造函数參数的须要,最简单的就是java中的解决方法,即避免在虚基类中放入数据成员,java中的虚基类 接口禁止包括数据。

虚函数的优先度。当虚函数在多重继承中涉及到虚基类时,会有优先度的问题,仍然以ABCD为例,A中有虚函数void fun(),C中重定义了fun,但B和D中没有,调用D的指针fun时,若A不是虚基类,则是正常的情况,发生二义性错误。可是当A是虚基类时,就能够说C中重定义的fun的优先度高于最初的A中的定义也是B中的fun,则此时会无二义性的解析为调用 C::fun()函数。

当对于原 B类继承于A类,现今须要新加入一个新类C继承与A,可是你发现其与B类用很多相似的地方,但C又不是一个B,所以你决定让C 由B实现,便让C私有继承于B,同一时候继承A,同一时候改动了一下B中的虚函数,实现了多继承。而还有一种做法是将BC的共同点放在一个新的类D中,改变继承结构,使D继承于A,而BC继承于D,这样就仅仅有单继承了。表面上看来,多继承没有加入一个新的类,没有改变原有的继承结构,它仅仅是在原有的类B的基础上添加了一些新的虚函数,这样看似添加了大量的功能,而仅仅添加了一点点复杂性。但事实让,引入多继承就会带来很多麻烦。

MI是复杂的,但也是实用的,须要明智的去使用。

44.说你想说的,理解你想说的。

简单来说,理解面向对象构件在c++中的含义,而不是仅仅去记忆c++的语言规则。对c++理解越深,越能清晰的考虑问题。

Effective C++ 43,44的更多相关文章

  1. Effective STL 43: Prefer algorithm calls to hand-written loops

    Effective STL 43: Prefer algorithm calls to hand-written loops */--> div.org-src-container { font ...

  2. EC读书笔记系列之17:条款41、42、43、44、45、46

    条款41 了解隐式接口与编译器多态 记住: ★classes和templates都支持接口和多态 ★对classes而言接口是显式的(explicit),以函数签名为中心.多态则是通过virtual函 ...

  3. [Effective JavaScript 笔记]第44条:使用null原型以防止原型污染

    第43条中讲到的就算是用了Object的直接实例,也无法完全避免,Object.prototype对象修改,造成的原型污染.防止原型污染最简单的方式之一就是不使用原型.在ES5之前,并没有标准的方式创 ...

  4. Effective C++ 35,36,37

    35.使公有继承体现 "是一个" 的含义. 共同拥有继承意味着 "是一个".如  class B:public A. 说明类型B的每个对象都是一个类型A的对象, ...

  5. Effective C++ 24,25

    24.在函数重载和设定參数缺省值间要谨慎选择. 获得一种类型的数据的最小值或最大值,对于c中,一般使用在<linits.h>中定义的各种宏如INT_MIN 来进行表示,可是这样无法进行泛型 ...

  6. Effective C++ 49,50

    49.熟悉标准库. C++标准库非常大. 首先标准库中函数非常多,为了避免名字冲突.使用命名空间std.而之前的库函数都存放于< .h>中,如今成为伪标准库.而不能直接将这些头文件所有直接 ...

  7. [CSP模拟测试43、44]题解

    状态极差的两场.感觉现在自己的思维方式很是有问题. (但愿今天考试开始的一刻我不会看到H I J) A 考场上打了最短路+贪心,水了60. 然而正解其实比那30分贪心好想多了. 进行n次乘法后的结果一 ...

  8. 温控器/胎压检测/电表/热泵显示控制器等,低功耗高抗干扰断/段码(字段式)LCD液晶显示驱动IC-VK2C22A/B,替代市面16C22,44*4/40*4点显示

    产品品牌:永嘉微电/VINKA 产品型号:VK2C22A/B 封装形式:LQFP52/48 产品年份:新年份 概述: VK2C22是一个点阵式存储映射的LCD驱动器,可支持最大176点(44SEGx4 ...

  9. Tubian0.43,完善对QQ微信的支持

    Sourceforge.net下载:https://sourceforge.net/projects/tubian/ 123网盘下载: https://www.123pan.com/s/XjkKVv- ...

随机推荐

  1. (原)neuq oj 1022给定二叉树的前序遍历和后序遍历确定二叉树的个数

    题目描述 众所周知,遍历一棵二叉树就是按某条搜索路径巡访其中每个结点,使得每个结点均被访问一次,而且仅被访问一次.最常使用的有三种遍历的方式: 1.前序遍历:若二叉树为空,则空操作:否则先访问根结点, ...

  2. (原)剑指offer之旋转数组

    题目描述 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转. 输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素. 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋 ...

  3. redis和memcached的优缺点及区别

    1. 使用redis有哪些好处? (1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) (2) 支持丰富数据类型,支持string,li ...

  4. adb shell am/pm 常用命令详解与使用

    一.adb shell am 使用此命令可以从cmd控制台启动 activity, services:发送 broadcast等等 1.am start <packageName/.classN ...

  5. HDU 3480 DP 斜率优化 Division

    把n个数分成m段,每段的值为(MAX - MIN)2,求所能划分得到的最小值. 依然是先从小到大排个序,定义状态d(j, i)表示把前i个数划分成j段,所得到的最小值,则有状态转移方程: d(j, i ...

  6. 【UML】UML图的发展和体系结构

    导读:上次给徒弟验收UML的项目,在验收的时候提出了很多问题,徒弟也暴露了一些问题.说好我们一起总结成长的,由于最近的事儿,比较忙,所以现在进行总结.上次会议中说到要用门卫思维去总结这部分的知识点,用 ...

  7. BZOJ2241 [SDOI2011]打地鼠 【模拟】

    题目 打地鼠是这样的一个游戏:地面上有一些地鼠洞,地鼠们会不时从洞里探出头来很短时间后又缩回洞中.玩家的目标是在地鼠伸出头时,用锤子砸其头部,砸到的地鼠越多分数也就越高. 游戏中的锤子每次只能打一只地 ...

  8. [SDOI2008]仪仗队 (欧拉函数)

    题目描述 作为体育委员,C君负责这次运动会仪仗队的训练.仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图 ...

  9. FOJ Problem 2271 X

    Problem 2271 X Accept: 55    Submit: 200Time Limit: 1500 mSec    Memory Limit : 32768 KB Problem Des ...

  10. R语言入门视频笔记--1

    一.数据框简要 可输入来访问mtcars这个系统自带的数据框中的mpg列 mtcars$mpg 或者输入 mtcars[c("mpg","cyl")] 来访问两 ...