一.多重继承
•一个类可以同时从多个基类继承实现代码
–课堂教学系统
–交通工具系统
–零件装配系统
二.内存布局与类型转换
•子类对象中的多个基类子对象,按照继承表的顺序依次被构造,并从低地址到高地址排列,析构的顺序则与构造严格相反
内存布局与类型转换(续1)
•将继承自多个基类的子类类型的指针,隐式或静态转换为它的基类类型,编译器会根据各个基类子对象在子类对象中的内存布局,
进行适当的偏移计算,以保证指针的类型与其所指向目标对象的类型一致
•反之,将该子类的任何一 个基类类型的指针静态转 换为子类类型,编译器同 样会进行适当的偏移计算
•无论在哪个方向上,重解 释类型转换都不进行任何 偏移计算
三.名字冲突
•如果在子类的多个基类中,存在同名的标识符,而且子类又没有隐藏该名字,那么任何试图在子类中,
或通过子类对象访问该名字的操作,都将引发歧义,除非通过作用域限定操作符“::”显式指明所属基类
–class Real
{
public:
void add (Real const& real){ ... }
};
–class Imag
{
public:
void add (Imag const& imag) { ... }
};
–class Complex : public Real, public Imag { ... };
–Real r (1);
Imag i (2);
Complex c (3, 4);
c.add (r); // 错误
c.add (i); // 错误
c.Real::add (r);
c.Imag::add (i);
•如果无法避免基类中的名字冲突,最简单的方法是在子类中隐藏这些标识符,或借助using声明令其在子类中重载
–class Complex : public Real, public Imag
{
public:
void add (Real const& real) { ... }
void add (Imag const& imag) { ... }
};
–class Complex : public Real, public Imag
{
public:
using Real::add;
using Imag::add;
}
一.公共基类
•一个子类继承自多个基类,而这些基类又源自共同的祖先(公共基类),这样的继承结构称为钻石继承
二.多个公共基类子对象
•派生多个中间子类的公共基类子对象,在继承自多个中间子类的汇聚子类对象中,存在多个实例
•在汇聚子类中,或通过汇聚子类对象,访问公共基类的成员,会因继承路径的不同而导致不一致
一.共享公共基类子对象
•通过虚继承,可以保证公共基类子对象在汇聚子类对象中,仅存一份实例,且为多个中间子类子对象所共享
二.继承表中的virtual关键字
•为了表示虚继承,需要在继承表中使用virtual关键字
–class A
{
public:
A (int data) : m_data (data) {}
};
–class X : virtual public A
{
public:
X (int data) : A (data) {}
};
–class Y : virtual public B
{
public:
Y (int data) : A (data) {}
};
–class Z : public X, public Y
{ ... };
三.末端子类负责构造虚基类
•一般而言,子类的构造函数不能调用其间接基类的构造函数。但是,一旦这个间接基类被声明为虚基类,它的所有子类(无论直接的还是间接的)都必须显式地调用该间接基类的构造函数。否则,系统将试图为它的每个子类对象调用该间接基类的无参构造函数
–class A { ... };
–class X : virtual public A { ... };
–class Y : virtual public A { ... };
–class Z : public X, public Y
{
public:
Z (int data) : X (data), Y (data), A (data) { ... }
};
四.虚基类子对象的拷贝
•虚基类的所有子类(无论直接的还是间接的)都必须在其拷贝构造函数中显式指明以拷贝方式构造该虚基类子对象,否则编译器将选择以缺省方式构造该子对象
–class A { ... };
–class X : virtual public A { ... };
–class Y : virtual public A { ... };
–class Z : public X, public Y
{
public:
Z (Z const& that) : X (that), Y (that), A (that) { ... }
};
五.虚基类子对象的赋值
•与构造函数和拷贝构造函数的情况不同,无论是否存在虚基类,拷贝赋值运算符函数的实现没有区别
–class Z : public X, public Y
{
public:
Z& operator= (Z const& rhs)
{
if (&rhs != this)
{
X::operator= (rhs);
Y::operator= (rhs); // A::operator= (rhs); // 不需要
...
}
return *this;
}
};
六.虚继承对象模型
•汇聚子类对象中的每个中间子类子对象都持有一个虚表指针,该指针指向一个被称为虚表的指针数组的中部,
该数组的高地址侧存放虚函数指针,低地址侧存放虚基类子对象相对于每个中间子类子对象起始地址的偏移量
•某些C++实现会将虚基类子对象的绝对地址直接存放在中间子类子对象中,而另一些实现(比如微软)则提供了单独的虚基类表,但它们的基本原理都是类似的