多重继承的问题,首先想到多继承构造函数如何处理?
Symbian下是可以一个C类多个M类多重继承的
C++中的多继承,构造函数处理并没有问题,对象构造的时候按照继承中声明的顺序调用多个父类的构造函数,析构函数同样遵守单继承中的原则。
二意性问题
如果多基类中存在同名成员,会产生二意性的问题
比如,Root1类中声明DoAny()接口,Root2类中也声明了DoAny()接口,Child多承继Root1和Root2,那么如果Child对象直接调用DoAny接口就会产生编译错误。
深入原因??
对象值给父类指针、引用产生的二义性问题
继续上面的例子,Child对象的引用可以赋给Root1和Root2类引用。
如果存在void display(const Root1&),void display(cont Root2&)两个接口,那么Child child; display(child);这个调用也会产生二义性错误,编译器不知道应该调用哪个接口。
名字查找过程
查找过程是一个域一个域的查找,直到全局域。每个类都有一个类域。看如下示例代码:
class ZooAnimal {
public:
void print() const;
// 为了向外域暴露设为 public
string is_a;
int ival;
private::
double dval;
};
class Bear : public ZooAnimal {
public:
void print() const;
int mumble( int );
// 为了向外域暴露设为 public
string name;
int ival;
};
Bear bear; bear.is_a;
如上调用,is_a的名字解析过程为:
1. 首先查找Bear类域,没有找到
2. 查找Bear父类ZooAnimal类域,找到其声明,名字解析成功。
bear.ival;
这个调用解析过程如何呢?因为Bear和其父类ZooAnimal里都有该成员定义。答案是按照上的顺序查找,查找到马上返回。
1. Bear类域中查找,找到ival声明,解析成功,不再向下查找。那么父类中名字就被子类中定义屏蔽了。
需要用bear.ZooAnimal::ival的形式来域限定符访问ZooAnimal类中成员ival,这样编译器直接在ZooAnimal类域中开始查找名字。
看如下调用各名字的位置:
int ival;
int Bear::mumble(int ival)
{
return ival + //形参
::ival + //全局成员
ZooAnimal::ival +
Bear::ival;
}
而如下名字解析会出错:
int dval;
int Bear::mumble(int ival)
{
return ival + dval;//----dval为ZooAnimal私有成员,错误
}
解析过程如下:1. 在Bear类域中查找,没有找到
2. 在ZooAnimal类域中查找,找到,但是因为私有成员不能访问,编译错误,不再向下查找。
3. 因为上一步,全局域中的名字就被屏蔽了。
多继承中的情况
多继承中,名字的解析的时候,对父类域的查找是同时进行的。同时查找Root1类域和Root2类域,如果发现找到两个同样的名字,那么由于二义性名字解析出错,产生编译错误。
========================更新========================
才现上次的研究还没有抓住多继承问题的重点,下面来一个新的总结:
1. 普通多继承
类同时继承自多个基类就是多继承,C++支持多继承。
但是需要注意多继承产生的二意性问题,如果两个基类都存在同名的成员,那么在子类中对它的访问就是有二义性错误的。名字解析过程中,同时找到两个满足条件的成员,这让编译器无法确定应该使用哪个。
其中比较典型的问题就是下面要讲到的:菱形继承问题。
2. Private、public、protected继承
2.1 private继承
与public继承的不同:
² 不是父类的子类型,也就是说不能赋值给父类指针或引用。
² 接口私有化。
通常多继承时会使用public继承a,private继承b的方式。
可以通过如下方式还原private基类中的成员访问级别:
public:
//using private father func
//You can level down the access level, butcan't level up it.
using ZooAnimal::GetLegs;//correct
但是,经过我的测试,你可以还原或降低原有的访问级别,不能提高访问级别。比如基类中本身是private方法,在public块中using这个方法是编译错误的。
2.2 public继承
普通的继承方式
2.3 protected继承
基类所有public成员成为派生类的protected成员。
3. 菱形继承
3.1 基本概念
菱形继承是指:有基类A,B、C继承自A,D多继承自B、C,于是最终派生类D由两条不同的继承线继承基类A。
这是多继承中典型需要考虑的问题,这种情况下D的实例对基类A成员的访问都是二义性错误的。
3.2 普通继承方式中存在的问题
D对象中存在A类的2份不同的对象。
存储2份A对象浪费空间。
A的构造函数会被调用2次。
引起二义性问题。
因为是2份不同的对象,2个对象中成员的值不一致。
为解决如上的种种问题,C++引用虚继承。
4. 虚继承
4.1 基本概念
虚继承的基类叫做虚拟基类,无论它在派生层次中出现多少次,只有一个共享的基类子对象被继承。
C++ Primmer书里不建议大量使用虚继承,会有效率问题。
4.2 构造、析构
虚继承时,需要由最终派生类主动调用虚基类的构造函数,否则编译错误。
因为,对于虚继承的情况编译器会阻止中间派生类调用虚基类的构造函数。
比如:
在类D的实例初始过程中,D为最终派生类,B、C为中间派生类,A为虚基类。
调用D的构造函数前,先找到它的基类B,调用B的构造函数,调用B构造函数前找到B的基类A,发现为虚继承关系,阻止B对A构造函数的调用。编译器会确定D有没有对A构造函数的调用,如没有编译报错。
虚基类的构造函数最先被调用,然后再按声明顺序构造非虚基类。于是构造函数是:A、B、C、D调用顺序。
析构函数是和构造相反的顺序进行。
4.3 成员可视性
有如下几种情况:
情况 | 普通继承 | 虚继承 |
A中的接口,未被B、C重载 | 二义性 | OK,调用A的接口 |
B重载了A的接口,C未重载 | 二义性 | OK,调用B的接口 |
B、C都重载的接口 | 二义性 | 二义性 |