1 多重继承
一个类可以同时从多个基类继承实现代码。
1.1 多重继承的内存布局
子类对象内部包含多个基类子对象。
按照继承表的顺序依次被构造,析构的顺序与构造严格相反。
各个基类子对象按照从低地址到高地址排列。
// miorder.cpp 多重继承:一个子类有多个基类
#include <iostream>
using namespace std;
class A {
public:
int m_a;
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
};
class B {
public:
int m_b;
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
};
class C {
public:
int m_c;
C() { cout << "C()" << endl; }
~C() { cout << "~C()" << endl; }
};
class D : public A, public B, public C { // 汇聚子类
public:
int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
D d; // |A基类子对象|B基类子对象|C基类子对象|m_d|-->|m_a|m_b|m_c|m_d|
cout << "汇聚子类对象d的大小:" << sizeof(d) << endl; // 16
D* pd = &d;
cout << "整个汇聚子类对象的地址 D* pd: " << pd << endl;
cout << "A基类子对象的首地址: " << &d.m_a << endl;
cout << "B基类子对象的首地址: " << &d.m_b << endl;
cout << "C基类子对象的首地址: " << &d.m_c << endl;
cout << "D类自己的成员变量&m_d: " << &d.m_d << endl;
return 0;
}
1.2 多重继承的类型转换
(1)将子类对象的指针,隐式转换为它的某种基类类型指针,编译器会根据各个基类子对象在子类对象中的位置,进行适当的偏移计算,以保证指针的类型与其所指向目标对象的类型一致。
反之,将任何一个基类类型的指针静态转换为子类类型,编译器,编译器同样会进行适当的偏移计算。
不建议使用:无论在哪个方向上,重解释类型转换reinterpret_cast都不偏移:
// convercmp.cpp 多重继承的类型转换
#include <iostream>
using namespace std;
class A {
public:
int m_a;
};
class B {
public:
int m_b;
};
class C {
public:
int m_c;
};
class D : public A, public B, public C { // 汇聚子类
public:
int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
D d; // |A基类子对象|B基类子对象|C基类子对象|m_d|-->|m_a|m_b|m_c|m_d|
cout << "汇聚子类对象d的大小:" << sizeof(d) << endl; // 16
D* pd = &d;
cout << "整个汇聚子类对象的地址 D* pd: " << pd << endl;
cout << "A基类子对象的首地址: " << &d.m_a << endl;
cout << "B基类子对象的首地址: " << &d.m_b << endl;
cout << "C基类子对象的首地址: " << &d.m_c << endl;
cout << "D类自己的成员变量&m_d: " << &d.m_d << endl;
cout << "---------------隐式转换---------------" << endl;
A* pa = pd;
cout << "D* pd--->A* pa: " << pa << endl;
B* pb = pd;
cout << "D* pd--->B* pb: " << pb << endl;
C* pc = pd;
cout << "D* pd--->C* pc: " << pc << endl;
cout << "---------------static_cast---------------" << endl;
D* p1 = static_cast<D*>(pa);
cout << "A* pa--->D* p1: " << p1 << endl;
D* p2 = static_cast<D*>(pb);
cout << "B* pb--->D* p2: " << p2 << endl;
D* p3 = static_cast<D*>(pc);
cout << "C* pc--->D* p3: " << p3 << endl;
cout << "---------------reinterpret_cast---------------" << endl;
pa = reinterpret_cast<A*>(pd);
cout << "D* pd--->A* pa: " << pa << endl;
pb = reinterpret_cast<B*>(pd);
cout << "D* pd--->B* pb: " << pb << endl;
pc = reinterpret_cast<C*>(pd);
cout << "D* pd--->C* pc: " << pc << endl;
return 0;
}
(2)引用的情况与指针类似,因为引用的本质就是指针。
1.3 多重继承的名字冲突
如果在子类的多个基类中,存在同名的标识符,那么任何试图通过子类对象,或在子类内部访问该名字的操作,都将引发歧义。
解决办法1:子类隐藏该标识符(因噎废食,不建议);
解决办法2:通过作用域限定操作符::显示指明所属基类。
// scope.cpp 多重继承的名字冲突问题 -- 作用域限定符
#include <iostream>
using namespace std;
class A { // 学生类
public:
int m_a;
int m_c; // 成绩
};
class B { // 老师类
public:
int m_b;
int m_c; // 工资
};
class D : public A, public B { // 助教类
public:
int m_d;
// int m_c; // 在业务上毫无意义,仅仅就是为了将基类的m_c隐藏
void foo() {
A::m_c = 100;
}
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
D d; // |A基类子对象|B基类子对象|m_d| --> |m_a m_c|m_b m_c|m_d|
cout << "汇聚子类对象d的大小:" << sizeof(d) << endl; // 20
d.B::m_c = 8000;
return 0;
}
2 钻石继承
一个子类继承自多个基类,而这些基类又源自共同的祖先,这样的继承结构称为钻石继承(菱形继承):
2.1 钻石继承的问题
公共基类子对象,在汇聚子类对象中,存在多个实例:
// diamond.cpp 钻石继承
#include <iostream>
using namespace std;
class A { // 公共基类(人类)
public:
int m_a; // 年龄
};
class X : public A { // 中间子类(学生类)
public:
int m_x;
};
class Y : public A { // 中间子类(老师类)
public:
int m_y;
};
class Z : public X, public Y { // 汇聚子类(助教类)
public:
int m_z;
void foo() {
X::m_a = 20; // 歧义
}
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
Z z; // |X中间子类子对象|Y中间子类子对象|m_z|-->
// |A公共基类子对象 m_x|A公共基类子对象 m_y|m_z|-->|m_a m_x|m_a m_y|m_z|
cout << "汇聚子类对象z的大小: " << sizeof(z) << endl; // 20
z.Y::m_a = 20; // 歧义
return 0;
}
3 虚继承
3.1 钻石继承的解决方法
在继承表中使用virtual关键字。
虚基类子对象,不在中间子类子对象中,保存在最后。
虚继承可以保证:
(1)公共虚基类子对象在汇聚子类对象中仅存一份实例
(2)公共虚基类子对象被多个中间子类子对象所共享
3.2 虚继承实现原理
汇聚子类对象中的每个中间子类子对象都持有一个指针,通过该指针可以获取 中间子类子对象的首地址 到 公共虚基类子对象 的首地址的 偏移量:
// virtualinherit.cpp 虚继承 -- 钻石继承问题的解决法门
// (1) 公共基类(A类)子对象 在 汇聚子类(Z类)对象中 只存在一份
// (2) 公共基类(A类)子对象 要被 多个中间子类(X/Y类)子对象 共享
#include <iostream>
using namespace std;
#pragma pack(1)
class A { // 公共基类(人类)
public:
int m_a; // 年龄
};
class X : virtual public A { // 中间子类(学生类)
public:
int m_x;
void setAge( /* X* this */ int age ) {
this->m_a = age; // (1) this (2) X中间子类子对象 (3) 指针1 (4) 偏移量
// (5)this+偏移量 (6)A公共基类子对象首地址 (7)m_a
}
};
class Y : virtual public A { // 中间子类(老师类)
public:
int m_y;
int getAge( /* Y* this */ ) {
return this->m_a; // (1) this (2) Y中间子类对象 (3) 指针2 (4) 偏移量
// (5)this+偏移量 (6)A公共基类子对象首地址 (7)m_a
}
};
class Z : public X, public Y { // 汇聚子类(助教类)
public:
int m_z;
void foo() {
m_a = 20;
}
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
Z z; // |X中间子类子对象|Y中间子类子对象|m_z|A公共基类子对象|-->
// |指针1 m_x|指针2 m_y|m_z|m_a|
cout << "汇聚子类对象z的大小: " << sizeof(z) << endl; // 32
z.setAge( 28 ); // setAge( &z, 28 );
cout << z.getAge() << endl; // getAge( &z );
return 0;
}
3.3 虚继承的构造函数
// virtualCons.cpp 虚继承的构造函数
#include <iostream>
using namespace std;
#pragma pack(1)
class A { // 公共虚基类
public:
int m_a;
};
// 开关量X=0;
class X : virtual public A { // 中间子类
public:
X() {
//【*】
//【int m_x;】
// if( 开关量X==0 ) { // 只有开关量X为0时,X类构造函数才会创建 A虚基类子对象
// 【A();】
// }
}
int m_x;
};
// 开关量Y=0;
class Y : virtual public A { // 中间子类
public:
Y() {
//【*】
//【int m_y;】
// if( 开关量Y==0 ) { // 只有开关量Y为0时,Y类构造函数才会创建 A虚基类子对象
// 【A();】
// }
}
int m_y;
};
class Z : public X, public Y { // 汇聚子类
public:
Z( ) {
// 开关量X=1,开关量Y=1 (将 开关量置为1 )
//【X();】
//【Y();】
//【int m_z;】
//【A();】
// 开关量X=0,开关量Y=0 (复位开关量)
}
int m_z;
};
int main( void ) {
X x; // |指针 m_x|A虚基类子对象| --> |指针 m_x|m_a|
cout << "x对象的大小: " << sizeof(x) << endl; // 16
Y y; // |指针 m_y|A虚基类子对象| --> |指针 m_y|m_a|
cout << "y对象的大小: " << sizeof(y) << endl; // 16
Z z; // |X中间子类子对象|Y中间子对象|m_z|A公共虚基类子对象|
// --> |指针 m_x|指针 m_y|m_z|m_a|
cout << "z对象的大小: " << sizeof(z) << endl;
return 0;
}
3.4 虚继承特点
虚基类子对象,不在中间子类子对象中,保存在最后。(通过上述开关量可知)