当多重继承进入设计,程序可能从一个以上的基类继承相同名称(如函数、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的组合。