Effective C++笔记: 继承和面向对象设计(四)

来源:互联网 发布:中国电信网络发展部 编辑:程序博客网 时间:2024/06/05 15:45

 

Item 40: 谨慎使用 multiple inheritance(多继承)

multiple inheritance(多继承)意味着从多于一个的 base class(基类)继承, 可能会导致较多的歧义。

成员函数歧义:

class BorrowableItem {             // something a library lets you borrow
public:
  void
checkOut();                 // check the item out from the library

  ...
};

class ElectronicGadget {
private:
  bool
checkOut() const;           // perform self-test, return whether

  ...                              // test succeeds
};

class MP3Player:                   // note MI here
 
public BorrowableItem,           // (some libraries loan MP3 players)
 
public ElectronicGadget
{ ... };                           // class definition is unimportant

MP3Player mp;

mp.checkOut();                     // ambiguous! which checkOut?

注意这个例子,即使两个函数中只有一个是可访问的,对 checkOut 的调用也是有歧义的。(checkOut BorrowableItem 中是 public(公有)的,但在 ElectronicGadget 中是 private(私有)的。)这与 C++ 解析 overloaded functions(重载函数)调用的规则是一致的:在看到一个函数的是否可访问之前,C++ 首先确定与调用匹配最好的那个函数。只有在确定了 best-match function(最佳匹配函数)之后,才检查可访问性。(注意,因此访问权限不能区分不同的函数)这目前的情况下,两个 checkOuts 具有相同的匹配程度,所以就不存在最佳匹配。因此永远也不会检查到 ElectronicGadget::checkOut 的可访问性。

为了消除歧义性,你必须指定哪一个 base class(基类)的函数被调用:

mp.BorrowableItem::checkOut();              // ah, that checkOut...

当然,你也可以尝试显式调用 ElectronicGadget::checkOut,但这样做会有一个 "you're trying to call a private member function"(你试图调用一个私有成员函数)错误代替歧义性错误。

 

成员变量的歧义:(采用虚拟继承消除)

class File { ... };
class InputFile: public File { ... };
class OutputFile: public File { ... };
class IOFile: public InputFile,
              public OutputFile
{ ... };

你拥有一个在一个 base class(基类)和一个 derived class(派生类)之间有多于一条路径的 inheritance hierarchy(继承体系)(就像上面在 File IOFile 之间,有通过 InputFile OutputFile 的两条路径)的任何时候,你都必须面对是否需要为每一条路径复制 base class(基类)中的 data members(数据成员)的问题。例如,假设 File class 有一个 data members(数据成员)fileNameIOFile 中应该有这个 field(字段)的多少个拷贝呢?一方面,它从它的每一个 base classes(基类)继承一个拷贝,这就暗示 IOFile 应该有两个 fileName data members(数据成员)。另一方面,简单的逻辑告诉我们一个 IOFile object(对象)应该仅有一个 file name(文件名),所以通过它的两个 base classes(基类)继承来的 fileName field(字段)不应该被复制。

C++ 在这个争议上没有自己的立场。它恰当地支持两种选项,虽然它的缺省方式是执行复制。如果那不是你想要的,你必须让这个 class(类)带有一个 virtual base class(虚拟基类)的数据(也就是 File)。为了做到这一点,你要让从它直接继承的所有的 classes(类)使用 virtual inheritance(虚拟继承):

class File { ... };
class InputFile: virtual public File { ... };
class OutputFile: virtual public File { ... };
class IOFile: public InputFile,
              public OutputFile
{ ... };

从正确行为的观点看,public inheritance(公有继承)应该总是 virtual(虚拟)的。但虚拟继承会付出额外的代价,因此,除非必需,否则不要使用 virtual bases缺省情况下,使用 non-virtual继承。第二,如果你必须使用 virtual base classes(虚拟基类),试着避免在其中放置数据。这样你就不必在意它的 initialization(初始化)和赋值所带来的问题。(初始化一个 virtual base(虚拟基)的职责由 hierarchy(继承体系)中 most derived class(层次最低的派生类)承担。这个规则中包括的含义:(1 从需要 initialization(初始化)的 virtual bases(虚拟基)派生的 classes(类)必须知道它们的 virtual bases(虚拟基),无论它距离那个 bases(基)有多远;(2 当一个新的 derived class(派生类)被加入继承体系时,它必须为它的 virtual bases(虚拟基)(包括直接的和间接的)承担 initialization responsibilities(初始化职责))。

 

一个采用多重继承的例子:(注意为什么此处采用多重继承)

IPerson是一个接口类,公有继承;

PersonInfo是一个帮助实现的工具类,因为要重新定义其中的virtual函数,因此采用private继承。(参见条款39

class IPerson {                            // this class specifies the
public:                                    // interface to be implemented
  virtual ~IPerson();

  virtual std::string name() const = 0;
  virtual std::string birthDate() const = 0;
};

class DatabaseID { ... };                  // used below; details are
                                           // unimportant

class PersonInfo {                         // this class has functions
public:                                    // useful in implementing
  explicit PersonInfo(DatabaseID pid);     // the IPerson interface
  virtual ~PersonInfo();

  virtual const char * theName() const;
  virtual const char * theBirthDate() const;

  virtual const char * valueDelimOpen() const;
  virtual const char * valueDelimClose() const;
  ...
};

 

class CPerson: public IPerson, private PersonInfo {   // note use of MI
public:
  explicit CPerson(    DatabaseID pid): PersonInfo(pid) {}
  virtual std::string name() const           // implementations
  { return PersonInfo::theName(); }          // of the required
                                             // IPerson member
  virtual std::string birthDate() const       // functions
  { return PersonInfo::theBirthDate(); }
private:        

// redefinitions of inherited virtual delimiter functions
  const char * valueDelimOpen() const { return ""; }   
  const char * valueDelimClose() const { return ""; }  
};

 

总结:

multiple inheritance(多继承)比 single inheritance(单继承)更复杂。它可能导致新的歧义问题和对 virtual inheritance(虚拟继承)的需要。

virtual inheritance(虚拟继承)增加了 size(大小)和 speed(速度)成本,以及 initialization(初始化)和 assignment(赋值)的复杂度。当 virtual base classes(虚拟基类)没有数据成员时,它是最适用的。

multiple inheritance(多继承)合理的用途:”public继承某个Interface class(接口类)”private inheritance(私有继承)某个协助实现的class”的组合。