红联Linux门户
Linux帮助

C++编程批评系列 继承的本质

发布时间:2006-05-03 14:04:06来源:红联作者:Myiozzdoc
Eiffel和C++都提供了多继承的机制。但Java却没有,因为它认为多继承会导致许多问题的出现。不过Java提供了接口(interface)作为一种替换机制,它类似于Objective C中的协议(protocol)。Sun宣称接口可以提供多继承所能提供的所有特性。

Sun所宣称的“多继承会带来许多的问题”这个观点是对的,尤其是在C++中用以实现多继承的方法更能说明这一点。那些看起来似乎使用多继承会比单继承更简单的理由,现在都以被证明是毫无意义。例如,如何制订对于从两个类之上继承得到的具有相同名字的数据项之间的策略?它们之间是否兼容?如果是的话,那他们是否应该被合并成为一个实体?如果不兼容,那应该如何区分它们?……这样的列表可以列出很长很长。

Java的接口机制也可以用以实现多继承,但它也有一个很重要的不同之处(与C++ 相比):继承中的接口必须是抽象的。由于使用接口并没有任何的实作,这就消除了需要从不同实作之间选择的可能。Java允许在接口中声明具有常数字段。当需要多继承时,他们就合并成为一个实体,这样也就不会导致歧义的产生。但是,当这些常数具有不同的值时,又有什么会发生呢?

由于 Java不支持多继承,我们就不可以像在C++和Eiffel中那样使用混合(mixin)了。混合是一种特性,它可以把从不同的类中得到的不同的非抽象的函数放到一起形成一个新的复杂的类。例如,我们可能希望从不同的源代码中导入一些utility函数。然而,我们也可以通过使用组合而不是继承来达到同样的效果,因此,这也就不会对Java构成一个重要的攻击了。

Eiffel在解决多继承问题时并没有导入一个单独的接口机制。

有些人可能认为,相对于多继承来说,单继承更优雅一些。这是一个很特别的观点。

BETA [Madsen 93]就属于认为“多继承不优雅”的那一种:“Beta中没有多继承,这主要是因为(对于多继承)缺乏一个深刻的理论上的理解,并且当前的(对于多继承的)建议在技术上看来也非常复杂”。他们引用了Flavors(一种可以将类混合在一起的语言)为证据。与Madsen相比,Flavors中的多继承与其顺序有关,也就是说,继承自(A,B)和继承自(B,A)是不一样的。

Ada95是另一种不支持多继承的语言。Ada95支持单继承,并把它叫做标记类型扩展(tagged type extension)。

另外一些人认为,对于某些特殊模型下的问题,多继承可以提供优雅的解法,因此为之付出的努力也是值得的。虽然上面所列出的关于多继承的问题列表并不完善,它仍然显示:与多继承相关的问题是可以被系统地辨识出来的,而一旦问题被确认,它们也就可以被优雅地解决。当[Sakkinen 92]对于多继承研究到达一个很深的程度后,它就得出了上述定义。

Eiffel中采用的方法是,多继承会引发一些有趣的且有挑战性的问题,然后再优雅地解决它们。程序员所需做的所有决定都被限制在类的继承子句中。它包括使用renaming来保证众多从继承中得来的同名特性最终成为具有不同名字的特性,对于继承而来的特性所施展的新的export策略:redefining和undefining,以及用来消除歧义的select。在所有的情况下,编译器都会为我们做好这一切,为了使得语义清晰而不管是选择使用fork或是join,程序员都具有完全的控制权。

C++ 中相对Eiffel来说有着另外一种不同的用于消除歧义的机制。在Eiffel中,在renames子句中,特性间必须有着不同的名字。在C++中,可以使用域解析操作符’::’来区分成员。Eiffel的做法好处在于,歧义在声明中就被消除掉了。Eiffel的继承子句相对C++的来说要复杂不少,但它的代码也显得更简单,更稳固,并更具弹性。这也就是声明方法与操作符方法相比的好处所在。在C++中,每次当我们碰到在多个成员间具有歧义时,我们必须在代码中使用域解析操作符。这经使得代码变得混乱不堪,影响其延展性,如果有其他地方的改变会影响歧义时,我们可能就需要在歧义可能出现的每个地方改变已有的代码。

依照[Stroustrup 94]中12.8节所说,ANSI委员会考虑过使用renaming,但是这个提议被委员会中的一个成员所阻塞掉了,他坚持让委员会中的其他成员用两周时间来好好地考虑这个问题。在12.8节中给出的例子显示了在没有显示的renaming的前提下,如何做可以得到同样的效果。问题在于,如果这都需要那些专家们使用两周来考虑如何实现,那留给我们的空间又有多少呢?

域解析操作符并不只是被用来消除多继承所带来的歧义。由于设计良好的语言可以避免歧义的出现,因此域解析操作符也就是一个丑陋的,加深复杂性的实作手法。

在C++中,“如何来声明多继承中的父类们”是一个很复杂的问题。它影响到了建构函数被调用的次序,当程序员确实想从子类转到父类时也会导致问题的出现。然而,我们也可以把这个称为不好的程序设计风格。

C++和Eiffel的另一个不同之处在于直接的重复继承,Eiffel中允许:
class B inherit A, A end 但
class B : public A, public A { };
却不被C++认可。
文章评论

共有 0 条评论