现在考虑多重继承MI(multiple inheritance)。程序可能从一个以上的基类继承相同的名称(如函数 typedef等)。那会导致较多歧义。考虑以下代码:
class A
{
public:
void fun();
….
};
class B
{
private:
bool fun()const;
};
class C:public A,public B //多重继承
{…};
C c;
c.fun(); //调用的是哪个类的fun()?
此例的函数调用有歧义的,即使两个函数只有一个能调用(A的为公有,而B却是私有的。)
。我们用C+重载函数的调用规则找出最佳匹配,但本例中的两函数却有相同的匹配度。所以B::fun()的可取用性也没被编译器审查。
为了解决这个歧义,我们必须明确调用,如 c.A::fun();当然也能c.B::fun(),但会出现尝试调用私有成员的错误。
MI继承一个以上的基类,而这些在继承体系中又有更高的基类,这会导致要命的”钻石多重继承”.
class File{…};
class InputFile:public File{…};
class OutFile:public File{…};
class IOFile:public InputFile,public OutFile{…};

如果任何基类和某派生类之间有一条以上的路线,这就要面对一个问题:基类成员是否应该经由每一条路线复制。如上,如果File中有一成员fileName,IOFile从每个基类继承就应该有两份该变量,但简单逻辑告诉我们,IOFile 对象应该只有一个名称,继承基类而来的成员不能重复。
C+的立场:两方法都支持。---缺省方法是执行复制。如果不想这么做,就应该让带有此数据的类成为虚基类。所有直接继承它的类采用虚继承:
class File{…};
class InputFile:virtual public File{…};
class OutFile:virtual public File{…};
class IOFile:public InputFile,public OutFile{…};

C+标准程序库内含一个多重继承体系,结构就如上图,只不过类都是类模版。分别是basic_ios,basic_istream,basic_ostream,basic_iostream,
从正确行为来看:公有继承应该总是virtual。
但正确性不是唯一 观点,为避免继承成员重复,编译器必须提供若干幕后工作,造成的后果是:1、虚继承的类产生的对象往往比非虚继承类产生的对象大。2、访问虚基类成员变量也相对慢。虽然细节和编译器有关,但结论是,得为虚继承付出代价。
虚继承的成本还包括其他方面(虚基类初始化):1、类若派生自虚基类而要初始化,就必认知其虚基类---无论基类有多远 。2、当新的派生类加入继承体系,必须承担虚基类(无论直或间接)的初始化责任。
使用忠告:1、如非必要不使用虚基类 2、如果必须使用虚基类,必免在其中放置数据。
考虑以下接口类:
class IPerson
{
public:
virtual~IPerson();
virtrualstd::string birth() const=0;
virtualst::string name() const=0;
};
因为抽象类,IPerson类必须以指针和引用来编写程序。我们使用工厂函数将“派生自IPerson的具象类”实体化。
//工厂函数,根据唯一数据库ID合建一个Person对象
//条款18解释为何返回类型不是原始指针 DBid (Datebase id)
std::str::shared_ptr<IPerson> makePerson(DBid pid);
//此函数从使用者手上取得id
DBid askDBid();
DBid id(askDBid());
std::str::shared_ptr<IPerson> pp(makePerson(id));
makePerson创建对象并返回指针,无疑有某些派生自IPerson的具象类。在makePerson中创建对象。
假设这个类为CPerson。它必须提供“继承自IPerson”的纯虚函数的实现代码。又假设有一个和数据库相关的类PersonInfo,提供CPerson所需要的东西。
class PerosnInfo
{
public:
explicitPersonInfo(DBid pid);
virtual~PersonInfo();
virtual const char* theName() const;
virtual constchar* theBirth() const;
…
private:
virtualconst char* valueDeliOpen() const; //详下
virtualconst char* valueDeliClose() const; //虽然返回char*而非string 只要合适也是可以的
};
valueDeliOpen()函数用于格式化数据库字段时输出界定符。如A-B C被格式化为[A-B C]
但因为不是每个人都喜欢默认的”[ ]”界定符,所以定义为虚函数---允许派生类重定义。
const char* PerosnInfo ::valueDeliOpen() const
{
return “[”; //缺省起始符号
}
const char* PerosnInfo ::valueDeliClose() const
{
return “]”; //缺省结束符号
}
const char* PerosnInfo ::theName() const
{
//保留缓冲区给返回值使用,因为是static,默认初始化全部为0
static charval[MAX_LEN];
std::strcpy(val,valueDeliOpen());
…..//将val字符串添加到name变量中
std::strcpy(val, valueDeliClose()); //写入结尾符
return val;
}
比如一个人名为abc,theName()函数返回值为[abc],也可能是“abc”.因为valueDeliOpen()是虚函数,所以theName的返回值不仅取决于PersonInfo也取决于PersonInfo派生下去的类。
CPerson和PerosnInfo的关系明显是根据某物实现出。TK39说明复合是比较好的方法。但如果我们要重新定义虚函数,继承是必须的。如本例中Cperson重新定义valueDeliOpen()等函数。单纯的复合无法满足要求。最直接的方法是CPerson私有继承自PersonInfo。但同时CPerson也必须实现IPerson的接口。故最后得出的方法是:
结合 公有继承接口和私有继承自某实现。
class IPerson
{
public:
virtual~IPerson();
virtrualstd::string birth() const=0;
virtualst::string name() const=0;
};
class DBid{….}; //后续使用,不在意细节
class PerosnInfo
{
public:
explicitPersonInfo(DBid pid);
virtual~PersonInfo();
virtualconst char* theName() const;
virtualconst char* theBirth() const;
virtualconst char* valueDeliOpen() const;
virtualconst char* valueDeliClose() const; //虽然返回char*而非string 只要合适//也是可以的
};
class CPerson:public IPerson,private PersonInfo //多重继承
{
public:
explicit CPerson(DBidpid):Person(pid); //实现必要的IPerson成员函数
virtualstd::string name()
{reurnPersonInfo::theName();}
virtual std::string birth()
{return PersonInfo::theBirth();}
virtual const char* valueDeliOpen()const {return “ ”;} //重新定义继承而来的虚函数
virtual const char* valueDeliClose()const {return “ ”;}
};
UML图如下:
这个例子说明多重继承也有它的合理用途。
需要记住的:
1、多重继承比单一继承复杂,可能导致新的歧义及对虚继承的需要。
2、虚继承会增加大小、速度及初始化复杂度等成本。当虚基类不带任何数据才是最具实用价值的情况。
3、在涉及公有继承接口,私有继承某协助实现的类时,多重继承很实用。