Effective C++笔记: 继承和面向对象设计(四)
2009-07-17 18:04
246 查看
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(数据成员)fileName。IOFile 中应该有这个 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”的组合。
相关文章推荐
- Effective C++笔记: 继承和面向对象设计(二)
- Effective C++笔记: 继承和面向对象设计(三)
- Effective C++笔记:继承与面向对象设计
- Effective C++笔记(8)—继承与面向对象设计
- Effective c++(笔记)之继承关系与面向对象设计
- Effective C++笔记(六):继承与面向对象设计
- Effective C++笔记: 继承和面向对象设计(一)
- effective C++ 学习笔记 实现&&继承与面向对象设计
- effective C++笔记之条款39: 避免“向下转换”继承层次
- effective c++条款32~40“继承与面向对象设计”整理
- effective C++笔记之条款35: 使公有继承体现“是一个”的含义
- Item 33:避免隐藏继承来的名称(继承与作用域嵌套) Effective C++笔记
- effective C++读书笔记六 —— 继承与面向对象设计
- effective C++笔记之条款43: 明智地使用多继承(MI)
- Effective C++(20) 继承与面向对象设计
- effective C++笔记之条款38: 绝不要重新定义继承而来的缺省参数值
- Effective C++笔记_条款33确定你的public继承塑模出is-a关系
- Item 39:明智地使用private继承 Effective C++笔记
- effective C++笔记之条款36、37: 区分接口继承和实现继承、绝不要重新定义继承而来的非虚函数
- [Effective C++笔记]条款39:明智而审慎地使用private继承