Skip to content

Latest commit

 

History

History
147 lines (122 loc) · 4.86 KB

File metadata and controls

147 lines (122 loc) · 4.86 KB

条款40:明智而谨慎地使用多重继承

当多重继承进入设计,程序可能从一个以上的基类继承相同名称(如函数、typedef等),那会导致较多的歧义,例如:

class BorrowableItem {
public:
  void checkOut();
  ...
};
class ElectronicGadget {
private:
  bool checkOut() const;
  ...
};
class MP3Player :
  public BorrowableItem,
  public ElectronicGadget {
  ...
};
MP3Player mp;
mp.checkOut();

此例中对checkOut的调用是有歧义的,即使MP3Player的checkOut是private的,这是因为C++的重载决议先寻找最佳匹配函数,再检查其可用性。为了解决这个歧义,你必须清楚指出你要调用哪个基类内的函数:

mp.BorrowableItem::checkOut();

多重继承会导致菱形继承:

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

C++可以让基类内的成员变量经由每一条继承路径被复制,也可以让IOFile的派生类共享一份成员变量。为了实现成员变量共享,你必须令File成为一个虚基类,为此,你必须使用虚继承:

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

对虚基类的使用会导致更多代价,还会导致初始化的一些问题。第一,非必要不使用虚基类,平时请使用non-virtual继承。第二, 如果必须使用虚基类,尽可能避免在其中放置数据,避免在这些class上初始化带来的诡异事情。

现在让我们看看下面这个C++ Interface class:

class IPerson {
public:
  virtual ~IPerson();
  virtual std::string name() const = 0;
  virtual std::string birthDate() const = 0;
};

为创建一些可被当作IPerson来使用的对象,使用工厂函数将派生自IPerson的具象class实例化:

std::shared_ptr<IPerson> makePerson(DatabaseID personIdentifier);

DatabaseID askUserForDatabaseID();

DatabaseID id(askUserForDatabaseID();)
std::shared_ptr<IPerson> pp(makePerson(id));

假设makePerson中派生自IPerson的具象class名为CPerson,为了实现CPerson,我们可以利用既有组件。假设有个既有的数据库相关class,名为PersonInfo,提供CPerson所需要的东西:

class PersonInfo {
public:
  explicit PersonInfo(DatabaseID pid);
  virtual ~PersonInfo();
  virtual const char* theName() const;
  virtual const char* theBirthDate() const;
  ...
private:
  virtual const char* valueDelimOpen() const;
  virtual const char* valueDelimClose() const;
  ...
};

PersonInfo被设计用来协助以格式打印数据库字段。两个virtual函数valueDelimOpen和valueDelimClose允许派生类设定它们自己的头尾界限。以 Person::theName 为例,代码看起来像这样:

const char* PersonInfo::valueDelimOpen() const { return "["; } 
const char* PersonInfo::valueDelimClose() const { return "]"; } 
const char* PersonInfo::theName() const { 
  static char value[Max_Formatted_Field_Value_Length];
  std::strcpy(value, valueDelimOpen());
  std::strcat(value, valueDelimClose());
  return value;
}

CPerson和PersonInfo的关系是,PersonInfo刚好有若干函数可帮助CPerson比较容易实现出来。他们的关系因此是is-implemented-in-terms-of。条款39指出,如果需要重新定义virtual函数,那么使用private继承是必要的。

但CPerson也必须实现IPerson接口,那需要使用public继承才能完成。这导致多重继承的一个合理应用:

class IPerson {
public:
  virtual ~IPerson();
  virtual std::string name() const = 0;
  virtual std::string birthDate() const = 0;
};
class DatabaseID {...};
class PersonInfo {
public:
  explicit PersonInfo(DatabaseID pid);
  virtual ~PersonInfo();
  virtual const char* theName() const;
  virtual const char* theBirthDate() const;
  ...
private:
  virtual const char* valueDelimOpen() const;
  virtual const char* valueDelimClose() const;
  ...
};

class Person : public IPerson, private PersonInfo {
public:
  explicit CPerson(DataBaseID pid) : PersonInfo(pid) {}
  virtual std::string name() const 
  { return PersonInfo::theName(); }
  virtual sts::string birthDate() const 
  { return PersonInfo::theBirthDate(); }
private:
  const char* valueDelimOpen() const { return ""; }
  const char* valueDelimClose() const { return ""; }
};

这个例子告诉我们,多重继承也有它的合理用途。

请记住

  • 多重继承比单一继承复杂。它可能导致新的歧义,以及对virtual继承的需要。
  • virtual继承会增加成本,如果虚基类不带任何数据,将是最具使用价值的情况。
  • 多重继承的确有正当用途。其中一个情节涉及public继承某个Interface class和private继承某个协助实现的class的组合。