首页 > 技术文章 > 【40】明智而审慎地使用多重继承

nzbbody 2014-01-03 21:18 原文

1、多种继承,首先带来一个问题。那就是子类会从多个父类中继承相同的名称。这样,在调用的时候,必然导致歧义。

2、考虑Base1中有个public方法XXX,Base2中有个private方法XXX,Derived继承Base1和Base2,那么分析一下,Derived d; d.XXX()到底调用哪个方法?

  你可能会想,虽然两个都匹配,但是只有Base1的XXX方法可访问,因此调用Base1的XXX方法。这种想法是错的,编译报错:对XXX的访问不明确。为什么?

  因为编译器决议调用哪个方法的时候,第一步,先把所有匹配的名称都拿来,不管是不是可以访问,然后在里面找到一个最优匹配,如果两个匹配程度一样,编译器就傻眼了,只能报错。再退一步说,C++过载不能跨域,即只能在当前类中过载。经过测试,从多个父类继承相同的名称,即使匹配程度不一样,也会导致对XXX的访问不明确。

  怎么解决这个问题呢?

  明确告诉编译器,我要调用哪个父类的方法。

  d.Base1::XXX(); //OK

  d.Base2::XXX(); //Error,无法访问成员XXX

  注:单一继承也可以使用d.Base1::XXX();这种形式。

  还有一个办法,子类定义一个XXX方法,隐藏两个父类的XXX方法,这样就不存在不明确的调用了。

3、钻石型多继承,考虑InputFile继承File,OutputFile继承File,IOFile继承InputFile和OutputFile,File中有个数据fileName,现在出问题了。从继承角度讲,IOFile有两份fileName,但根据常识,IOFile应该只有一个fileName。C++默认的做法是IOFile中有两个fileName。

4、如何解决上面的问题呢?也就是说,保证IOFile中只有一个fileName。通过使用virtual继承。让InputFile和OutputFile使用virtual继承,IOFile的继承方式不变。这样就使得IOFile中,只有一个fileName。显而易见,这种做法非常丑陋。而且,虽然起到了效果,要付出很大的代价。编译器在幕后做了一些事情,导致的结果是:InputFile的体积变大,访问变慢,初始化也可能带来诡异的问题。

5、因此,尽量少使用virtual继承。如果必须使用virtual继承,也要保证不在virtual Base(即File)中放置数据,从而保证IOFile中没有重复的数据。C#和Java中的interface就不允许有数据。相对而言,有重复的方法,还好办,可以指定调用哪个父类的方法。

6、那么,多重继承就一无是处吗?多重继承的使用场景:public继承一个类,暴露接口,同时private继承一个类,根据它来实现。你可能会想,根据某物实现出,不是应该优先使用复合技术吗?private继承可以解决两个问题:一是private继承可以访问protected成员,二是可以重写方法。

推荐阅读