王的男人
分类: C/C++
2015-07-03 16:45:27
原文地址:详解C++多重继承 作者:iWonderLinux
本文转载自:http://blog.csdn.net/wuliming_sc/article/details/3832583
多重继承
《C++ primer 3th》P794~798
为支持多继承,一个类的派生表:
class Bear : public ZooAnimal { ... };
被扩展成支持逗号分割的基类表。例如:
class Panda : public Bear, public Endangered { ... };
每个被列出的基类还必须指定其访问级别:public、protected 或private 之一。与单继承一样,只有当一个类的定义已经出现后,它才能被列在多继承的基类表中。
对于一个派生类的基类的数目,C++没有限制。实际情况下,两个基类是最常见的,一个基类常常用于表示一个公有抽象接口,另外一个基类提供私有实现。从三个或者更多个直接基类继承而来的派生类遵循mixin-based
设计风格,其中每个基类都表示该派生类完整接口的一个方面。
构造的次序
在多继承下,派生类含有每个基类的一个基类子对象。例如,当我们写:
Panda ying_yang;
时,ying_yang 由一个Bear类子对象(它又含有一个ZooAnimal 基类子对象)、一个Endangered 类子对象,以及在Panda 类中声明的非静态数据成员组成:
多继承Panda 层次结构
基类构造函数被调用的顺序以类派生表中声明的顺序为准。例如对ying_yang 来说,构造函数被调用的顺序是:Bear 构造函数(因为Bear 是从ZooAnimal 派生的,所以在Bear构造函数执行之前,ZooAnimal 的构造函数先被调用),Endangered 构造函数,然后是Panda构造函数。
构造函数调用顺序不受基类在成员初始化表中是否存在以及被列出的顺序的影响,也即是说,如果Bear 缺省构造函数被隐式调用,没有出现在成员初始化表中,如下所示:
// Bear 缺省构造函数在Endangered 的双参数构造函数之前被调用
Panda::Panda(): Endangered( Endangered::environment,Endangered::critical )
{ ... }
那么Bear 的缺省构造函数仍然在显式列出的双参数Endangered 构造函数之前被调用。类似地,析构函数调用顺序总是与构造函数顺序相反。在我们的例子中,析构函数调用顺序是:~Panda()、~Endangered()、~Bear(), 最后是~ZooAnimal()。
同名成员函数的二义性
在单继承下,基类的public 和protected 成员可以直接被访问,就像它们是派生类的成员一样,对多继承这也是正确的。但是在多继承下,派生类可以从两个或者更多个基类中继承同名的成员。然而在这种情况下,直接访问是二义的,将导致编译时刻错误。例如,如果Bear和Endangered 都定义了一个成员函数print(),则如下语句:
ying_yang.print( cout );
将导致编译时刻错误,即使这两个通过继承得到的成员函数定义了不同的参数类型:
Error: ying_yang.print( cout ) -- ambiguous, one of
Bear::print( ostream& )
ndangered::print( ostream&, int )
原因在于继承得到的成员函数没有构成派生类中的重载函数,因此,对于print()调用,编译器在解析的时候,只是使用了针对print 的名字解析,而不是使用“基于传递给print()的实际实参类型的重载解“。
转换与多个基类
在单继承下,如果有必要的话,派生类的指针或引用可以自动被转换成基类的指针或引用。对于多继承,这也是正确的。例如一个Panda指针或引用可以被转换成ZooAnimal、Bear或Endangered 类的指针或引用。例如:
extern void display( const Bear& );
extern void highlight( const Endangered& );
Panda ying_yang;
display( ying_yang ); // ok
highlight( ying_yang ); // ok
extern ostream& operator<<( ostream&, const ZooAnimal& );
cout << ying_yang << endl; // ok
但是在多继承下二义转换的可能性非常大。例如,考虑下列两个函数:
extern void display( const Bear& );
extern void display( const Endangered&);
用非限定修饰的Panda 对象调用display():
Panda ying_yang;
display( ying_yang ); // 错误: 二义性
将导致下列一般形式的编译时刻错误:
Error: display( ying_yang ) -- ambiguous, one of
extern void display( const Bear&);
extern void display( const Endangered&);
编译器没有办法区分应该使用哪一个直接基类(编译器不会试图根据派生类转换来区别基类间的转换,转换到每个基类都一样好)。
多重继承下的虚函数
为了了解多继承怎样影响虚拟函数机制,让我们为每个Panda 的直接基类定义一组虚拟函数:
class Bear : public ZooAnimal {
public:
virtual ~Bear();
virtual ostream& print( ostream&) const;
virtual string isA() const;
// ...
};
class Endangered {
public:
virtual ~Endangered();
virtual ostream& print( ostream&) const;
virtual void highlight() const;
// ...
};
现在我们来定义Panda,它提供了自己的print()实例、析构函数,并引入了一个新的虚拟函数cuddle():
class Panda : public Bear, public Endangered
{
public:
virtual ~Panda();
virtual ostream& print( ostream&) const;
virtual void cuddle();
// ...
};
可以直接从Panda 对象调用的虚拟函数集如下表所示:
虚拟函数名 |
活动实例 |
Destructor(构析函数) |
Panda::~Panda() |
print(ostream&) const |
Panda::print(ostream&) |
isA() const |
Bear::isA() |
highlight() const |
Endangered::highlight() |
cuddle() |
Panda::cuddle() |
当用Panda 类对象的地址初始化或赋值Bear 或ZooAnimal 指针或引用时,Panda 接口中“Panda 特有的部分“以及”Endangered 部分“就都不能再被访问。例如:
Bear *pb = new Panda;
pb->print( cout ); // ok: Panda::print(ostream&)
pb->isA(); // ok: Bear::isA()
pb->cuddle(); // 错误: 不是 Bear 接口的部分
pb->highlight(); // 错误: 不是 Bear 接口的部分
delete pb; // ok: Panda::~Panda()
类似地,当用Panda类对象的地址初始化或赋值Endangered 指针或引用时,Panda 接口中“Panda 特有的部分“以及”Bear 部分“都不能再被访问。例如:
Endangered *pe = new Panda;
pe->print( cout ); // ok: Panda::print(ostream&)
pe->isA(); // 错误: 不是 Endangered 的接口部分
pe->cuddle(); // 错误: 不是 Endangered 的接口部分
pe->highlight(); // ok: Endangered::highlight()
delete pe; // ok: Panda::~Panda()
无论我们删除对象所使用的指针类型是什么,虚拟析构函数的处理都是一致的。例如:
// ZooAnimal *pz = new Panda;
delete pz;
// Bear *pb = new Panda;
delete pb;
// Panda *pp = new Panda;
delete pp;
// Endangered *pe = new Panda;
delete pe;
在上述例子中,析构函数的调用顺序完全相同。析构函数调用顺序与构造函数的顺序相反:Panda析构函数被通过虚拟机制调用。在Panda 析构函数执行之后依次静态调用Endangered、Bear和ZooAnimal 析构函数。