===18.2 多继承===
基类构造函数被调用的顺序以类派生表中声明的顺序为准
private继承的话,原来父类的public ,protected成员继承到子类中,并成为private类型成员;
protected继承的话,原来父类的public ,protected成员继承到子类中,并成为protected类型成员;
public继承的话,原来父类的public ,protected成员继承到子类中,类型不变
===18.3.2 免除exempting 个别成员的私有继承影响===
class PeekbackStack : private IntArray {
public:
// 维持公有访问级别
using IntArray::size;
// ...
};
派生类只能将继承得到的成员恢复到原来的访问级别,该访问级别不能比基类中原来指定的级别更严格或更不严格
class Engine {
public:
Engine(int numCylinders){}
void start(); // Starts this Engine
};
void Engine::start()
{
cout<<"start!"<<endl;
}
class Car : private Engine { // Car has-a Engine
public:
Car() : Engine(8) { }
void run()
{
start(); //ok
}
};
Car car;
car.start();//错误start私有
car.run();
class Car : private Engine { // Car has-a Engine
public:
Car() : Engine(8) { }
using Engine::start; //将start设为原有的公有访问级别
};
Car car;
car.start();//ok
===18.4 继承下的类域===
1、在继承下,派生类的域被嵌套在直接基类的域中,如果一个名字在派生类域中没有被解析出来,则编译器在外围基类域中查找该名字的定义
2、名字解析的实际过程:
局部域->派生类->基类->全局域
编译器总是先解析一个类成员然后再判断该访问是否合法
class ZooAnimal {
private:
double dval;
};
class Bear : public ZooAnimal {
};
int dval;
int Bear::mumble( int ival )
{
// 错误: 解析为 ZooAnimal::dval 的私有成员函数
return ival + dval;
}
3、解析过程中,找到了名字则停止查找
int ival;
int Bear::mumble( int ival )
{
return ival + // 参数实例
::ival + // 全局实例
ZooAnimal::ival +
Bear::ival;
}
对ival 未限定修饰的引用被解析为形式参数实例,如果mumble()中没有定义ival 则访问
Bear 的ival 成员实例,如果在类Bear 中也没有定义ival 则访问ZooAnimal 的ival 成员实例,
如果在ZooAnimal 类中也没有定义ival 则访问全局的ival 实例
===18.4.1 多继承下的类域===
1、二义性问题:
class Endangered {
public:
ostream&print( ostream&) const;
void highlight();
// ...
};
class ZooAnimal {
public:
bool onExhibit() const;
// ...
private:
bool highlight( int zoo_location );
// ...
};
class Bear : public ZooAnimal {
public:
ostream&print( ostream&) const;
void dance( dance_type ) const;
// ...
};
当用多继承派生Panda 类时
class Panda : public Bear, public Endangered {
public:
void cuddle() const;
// ...
};
虽然从Bear 和Endangered 基类中继承print()和highlight()都存在潜在的二义性
但是在真正引用这些函数之前编译器并不会报告二义错误
print()成员的二义性是显然的,但是在两个highlight()之间的冲突有些令人吃惊,毕竟两个实例有不同的访问级别和不同的函数原型
在多继承下,查找过程对每个基类的继承子树***同时***进行检查
2、解决办法:
a、在程序层次上,解决成员二义性的方案是用类域操作符显式地限定修饰期望被调用的实例==>这种方法不推荐
b、在提供预期行为的派生类中定义一个同名实例(即在派生类中重新定义好有二义性的函数)
===18.5 虚拟继承===
在虚拟继承下只有一个共享的基类子对象被继承,如iostream对ios的继承,
而无论该基类在派生层次中出现多少次。共享的基类子对象被称为虚拟基类virtual base class
===18.5.1 虚拟基类声明===
下列声明使得ZooAnimal 成为Bear 和Raccoon 的虚拟基类
// 关键字 public 和 virtual的顺序不重要
class Bear : public virtual ZooAnimal { ... };
class Raccoon : virtual public ZooAnimal { ... };
===18.5.2 特殊的初始化语义===
在非虚拟派生中派生类只能显式初始化其***直接基类***
然而在虚拟派生中,只有Panda 可以直接调用其ZooAnimal 虚拟基类的构造函数,继承关系见图
虚拟基类的初始化变成了最终派生类most derived class 的责任,这个最终派生类是由每个特定类对象的声明来决定的
Bear winnie( "pooh" );//Bear为最终派生类,ZooAnimal构造由它负责
Raccoon meeko( "meeko" );//Raccoon为最终派生类,ZooAnimal构造由它负责
Panda yolo( "yolo" );//Panda为最终派生类,ZooAnimal构造由它负责,中间类Bear、Raccoon对虚拟基类构造函数的调用被自动抑制
如果Panda 的构造函数没有显式地为ZooAnimal 构造函数指定实参,则发生下面两个动
作之一,调用ZooAnimal 的缺省构造函数,或者如果没有缺省构造函数,则编译器在编译
Panda 构造函数的定义时会给出一个错误消息
===18.5.3 构造函数与析构函数顺序===
无论虚拟基类出现在继承层次中的哪个位置上,它们都是在非虚拟基类之前被构造
class Character { ... };
class BookCharacter : public Character { ... };
class Bear : public virtual ZooAnimal { ... };
class ToyAnimal { ... };
class TeddyBear : public BookCharacter, public Bear, public virtual ToyAnimal
{ ... };
已知声明
TeddyBear Paddington;
基类构造函数的调用顺序如下:
ZooAnimal(); // Bear 的虚拟基类
ToyAnimal(); // 直接虚拟基类
Character(); // BookCharacter 的非虚拟基类
BookCharacter(); // 直接非虚拟基类
Bear(); // 直接非虚拟基类
TeddyBear(); // 最终派生类
===18.5.4 虚拟基类成员的可视性===
在虚拟派生下,对于虚拟基类成员的继承,比该成员后来重新定义的实例的权值小
// ok: 在虚拟继承下没有二义
// 调用 Bear::onExhibit(),不会调用虚拟基类的ZooAnimal::onExhibit()
yolo.onExhibit();