C++之多重继承
C++中,所谓的多继承是指一个派生类可以有多个基类,这样就可能带来以下几方面的问题:
(1)多个基类中存在相同名称时
如果一个派生类继承的多个基类中包含有相同名称的函数时,有可能会产生调用不明确,即发生歧义,比如如下代码:
1 class BorrowableItem 2 { 3 public: 4 void checkOut(); 5 }; 6 7 class ElectronicGadget 8 { 9 public: 10 bool checkOut() const; 11 }; 12 13 class MP3Player : public BorrowableItem, public ElectronicGadget{ ... }; 14 15 MP3Player mp3; 16 mp3.checkOut();
上述代码中对象mp3调用函数checkOut时就会产生歧义,因为它的基类中都含有函数checkOut。编译器在查找可用的函数时,首先查找最佳匹配函数,然后再检查其是否可用,上面的两个基类的函数具有相同的匹配度,没有所谓的最佳匹配,因而编译器也不会检查可取用性。
为了指明到底是调用哪个基类的函数,通常需要如下指定:
mp3.BorrowableItem.checkOut();
(2)钻石继承体系
如果一个派生类继承的多个基类中,向上还要继承其他的类,例如
class File { }; class InputFile : public File { ... }; class OutputFile : public File { ... }; class IOFile : public InputFile, public OutputFile { ... };
这种形式的继承像极了钻石,因而也称钻石继承:
这种继承带来了这样的问题:如果一个继承体系中从一个base class到一个derived class有一个以上的通路,那么该让base class的成员变量在每一个通路中的derived class中都复制一次吗?如果是,那么这种复制必然会带来不必要的空间开销, 例如在上面的继承体系中,如果File中包含有一个fileName变量,那么IOFile类中就会包含两份fileName变量的空间,这在现实中显然是不合理的。
对此,C++提供两种方案:第一种缺省做法是提供复制,即上面的做法;第二是对基类进行virtual继承,形式如下:
1 class File { ... }; 2 class InputFile : virtual public File { ... }; 3 class OutputFile : virtual public File { ... }; 4 class IOFile : public InputFile, public OutputFile { ... };
这种改变理论上来讲总是合理的,即public继承应当为virtual public继承,但是之所以不这么做的原因是,为了处理成员变量重复的问题,编译器需要做很多复杂的事情,对应的产生的派生类对象的体积也更大,访问virtual base class成员变量的速度也更慢。
此外,对virtual base class的初始化也更加复杂,其初始化责任由最底层的派生类承担(一般的继承中不是如此吗?)。
基于以上,可以看到,如果必须使用virtual base class,那么最好在virtual base class中少放数据,以避免初始化时可能带来的诡异行为。
(3)多重继承的合理性
如果一个类的许多实现可以通过使用已有的类的实现达到便利,即"is-implemented-in-terms-of",那么可能需要private继承,当然这里也可以使用复合加public继承,假如这里执意用private继承。另一方面,该类还需要实现某个接口类,即必须通过public继承某个包含许多接口的类,那么多重继承看来就是合理的:
1 class IPerson 2 { 3 public: 4 virtual ~IPerson(); 5 virtual string name() const = 0; 6 virtual string birthDate() const = 0; 7 }; 8 9 class DatabaseID { ... }; 10 class PersonInfo 11 { 12 public: 13 explicit PersonInfo(DatabaseID pid); 14 virtual ~PersonInfo(); 15 virtual const char* theName() const; 16 virtual const char* theBirthDate() const; 17 virtual const char* valueDelimOpen() const; 18 virtual const char* valueDelimClose() const; 19 ... 20 }; 21 22 class CPerson : public IPerson, private PersonInfo 23 { 24 public: 25 explicit CPerson(DatabaseID pid) : PersonInfo(pid) { } 26 virtual string name() const 27 { 28 return PersonInfo::theName(); 29 } 30 virtual string birthDate() const 31 { 32 return PeronInfo::theBirthDate(); 33 } 34 35 private: 36 const char* valueDelimOpen() const { return ""; } 37 cpnst char* valueDelimClose() const { return ""; } 38 };
总结来讲,多重继承会产生歧义性,可能需要引入virtual base class,而virtual base class又可能造成大小、速度、初始化复杂度增加的成本,因而如果能避免使用多重继承,就应该避免。在某写场合下,比如需要public继承一个借口类和private继承一个协助实现的类时,采用多重继承会带来便利。
以上整理自Effective C++中文版第三版case 40.