目录
多继承
C++ 除了支持单继承外,还支持多重继承。那为什么要引入多重继承呢?其实是因为在客观现实世界中,我们经常碰到一个人身兼数职的情况,如在学校里,一个同学可能既是一个班的班长,又是学生中某个部门的部长;在创业公司中,某人既是软件研发部的 CTO ,又是财务部的 CFO ;一个人既是程序员,又是段子手。诸如此类的情况出现时,单一继承解决不了问题,就可以采用多基继承了。
继承关系本质上是一个IS A的关系。
多重继承的派生类对象的构造和析构
多继承的定义方式
class A
{
public:
A(){ cout << "A()" << endl; }
~A(){ cout << "~A()" << endl; }
void print() const{
cout << "A::print()" << endl;
}
};
class B
{
public:
B(){ cout << "B()" << endl; }
~B(){ cout << "~B()" << endl; }
void show() const{
cout << "B::show()" << endl;
}
};
class C
{
public:
C(){cout << "C()" << endl; }
~C(){ cout << "~C()" << endl; }
void display() const{
cout << "C::display()" << endl;
}
};
class D
: public A,B,C
{
public:
D(){ cout << "D()" << endl; }
~D(){ cout << "~D()" << endl; }
//void print() const{
// cout << "D::print()" << endl;
//}
};
如果这样定义,那么D类公有继承了A类,但是对B/C类采用的默认的继承方式是private
如果想要公有继承A/B/C三个类
class D
: public A
, public B
, public C
{
public:
D(){ cout << "D()" << endl; }
~D(){ cout << "~D()" << endl; }
//void print() const{
//cout << "D::print()" << endl;
//}
};
此结构下创建D类对象时,这四个类的构造函数调用顺序如何?
马上调用D类的构造函数,在此过程中会根据继承的声明顺序,依次调用A/B/C的构造函数,创建出这三个类的基类子对象
D类对象销毁时,这四个类的析构函数调用顺序如何?
马上调用D类的析构函数,析构函数执行完后,按照继承的声明顺序的逆序,依次调用A/B/C的析构函数
多重继承可能引发的问题
成员名访问冲突的二义性
解决成员名访问冲突的方法:加类作用域(不推荐)—— 应该尽量避免。
同时,如果D类中声明了同名的成员,可以对基类的这些成员造成隐藏效果,那么就可以直接通过成员名进行访问。
D d;
d.A::print();
d.B::print();
d.C::print();
d.print(); //ok
存储二义性的问题(重要)
菱形继承结构
class A
{
public:
void print() const{
cout << "A::print()" << endl;
}
double _a;
};
class B
: public A
{
public:
double _b;
};
class C
: public A
{
public:
double _c;
};
class D
: public B
, public C
{
public:
double _d;
};
菱形继承情况下,D类对象的创建会生成一个B类子对象,其中包含一个A类子对象;还会生成一个C类子对象,其中也包含一个A类子对象。所以D类对象的内存布局中有多个A类子对象,访问继承自A的成员时会发生二义性(无论是否涉及A类的数据成员,单纯访问A类的成员函数也会冲突)
因为编译器需要通过基类子对象去调用,但是不知道应该调用哪个基类子对象的成员函数。
当然,D类如果再写一个同名成员函数,会发生隐藏。或者使用作用域限定的方式访问 print() 。
解决存储二义性的方法:中间层的基类采用虚继承顶层基类的方式解决存储二义性
class A
{
public:
void print() const{
cout << "A::print()" << endl;
}
double _a;
};
class B
: virtual public A
{
public:
double _b;
};
class C
: virtual public A
{
public:
double _c;
};
class D
: public B
, public C
{
public:
double _d;
};
采用虚拟继承的方式处理菱形继承问题,实际上改变了派生类的内存布局。B类和C类对象的内存布局中多出一个虚基类指针,位于所占内存空间的起始位置,同时继承自A类的内容被放在了这片空间的最后位置。D类对象中只会有一份A类的基类子对象。
通过VS可以验证,查看D类的布局:
![]()
![]()
重新生成解决方案,然后再输出栏 ctrl+f 然后输入要查看的class的结构就可以找到了。
验证得到的结果: