多重继承
定义:一个派生类有两个或多个基类,派生类从两个或多个基类继承所需的属性。(允许一个派生类同时继承多个基类)
声明多重继承的方法
class D:public A,protected B,private C
{
类D新增加的成员
}
D是多重继生的派生类,它以公用继承的方法继承A,以保护继承方式继承B,以私有继承方法继承C。
多重继承派生类的构造函数
多重继承派生类的构造函数形式与单继承时的构造函数形式基本相同,只是在初始表中包含多个基类构造函数。
派生类构造函数名(总参数表):基类1构造函数名(参数表),基类2构造函数名(参数表)
{
派生类新增加的数据成员初始化语句;
}
- 构造函数初始化列表中各基类的排列顺序任意。
- 派生类构造函数的执行顺序依然是先调用基类的构造函数,再执行派生类构造函数的函数体。
- 调用基类构造函数的顺序是按照声明派生类基类出现的顺序。
例如:上面声明派生类D时,基类出现的顺序为 A,B,C,则先调用基类A的构造函数,再调用基类B的构造函数,最后调用基类C的构造函数。
#include<iostream>
#include<vector>
using namespace std;
class A
{
public:
A(int data = 0) :ma(data) { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
private:
int ma;
};
class B : public A
{
public:
B(int data = 0) :A(data), mb(data) { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
private:
int mb;
};
class C : public A
{
public:
C(int data = 0) :A(data), mc(data) { cout << "C()" << endl; }
~C() { cout << "~C()" << endl; }
private:
int mc;
};
class D : public B, public C
{
public:
D(int data = 0) :B(data), C(data), md(data) { cout << "D()" << endl; }
~D() { cout << "~D()" << endl; }
private:
int md;
};
int main()
{
D d;
return 0;
}
由图可知,类D保留了类A的两份数据成员。这就是多重继承的一个问题:从不同的基类会继承重复的数据。
多重继承引起的二义性问题
定义:继承的成员同名
#include<iostream>
#include<vector>
using namespace std;
class A
{
public:
int a;
void show()
{
cout << "show A\n";
}
};
class B
{
public:
int a;
void show()
{
cout << "show B\n";
}
};
class C :public A, public B
{
public:
int b;
void show()
{
cout << "show C\n";
}
};
int main()
{
C c1;
c1.a = 1; //error,由于基类A和基类B都由数据成员a,编译系统无法判断要访问的是哪一个基类
的成员。
c1.A::a=1; //ok,引用c1对象中的基类A的数据成员a
return 0;
}
虚继承
通过前面的介绍可知:
如果一个派生类由多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。
菱形继承的缺点:
- 保留多份数据成员的拷贝,占用较多的存储空间
- 增加了访问这些成员的时的困难
解决办法:C++提供虚基类的方法,使得在继承间接共同基类时只保留一份成员。
#include<iostream>
#include<vector>
using namespace std;
class A
{
public:
A(int data = 0) :ma(data) { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
private:
int ma;
};
class B : virtual public A
{
public:
B(int data = 0) :A(data), mb(data) { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
private:
int mb;
};
class C : virtual public A
{
public:
C(int data = 0) :A(data), mc(data) { cout << "C()" << endl; }
~C() { cout << "~C()" << endl; }
private:
int mc;
};
class D : public B, public C
{
public:
D(int data = 0) :B(data), C(data), md(data),A(data) { cout << "D()" << endl; }
~D() { cout << "~D()" << endl; }
private:
int md;
};
int main()
{
D d;
return 0;
}
可以看出类A只构造和析构了1次,而不是未加virtual之前的2次。
注意:
-
虚继类并不是在声明基类时声明的,而是声明派生类时,指定继承方式时声明的。
因为一个基类可以在生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。 -
通过虚继承,基类通过多条派生路将被一个派生类继承时,该派生类只继承该基类一次(基类成员只保留一次)
-
为了保证虚继类在派生类只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则依然会出现对基类的多次继承。
-
在定义类D的构造函数时,与以往使用的方法有所不同。规定:
在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C)
虚继承实现原理
普通继承的内存分布
#include<iostream>
using namespace std;
class A
{
public:
A(int data = 0) :ma(data) { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
private:
int ma;
};
class B : public A
{
public:
B(int data = 0) :A(data), mb(data) { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
private:
int mb;
};
class C : public A
{
public:
C(int data = 0) :A(data), mc(data) { cout << "C()" << endl; }
~C() { cout << "~C()" << endl; }
private:
int mc;
};
class D : public B, public C
{
public:
D(int data = 0) :B(data), C(data), md(data){ cout << "D()" << endl; }
~D() { cout << "~D()" << endl; }
private:
int md;
};
int main()
{
//D d;
//cout << sizeof(B) << endl;
cout << sizeof(D) << endl;
//cout << sizeof(int*) << endl;
return 0;
}
类D的内存布局:
类B的内存布局:
类C的内存布局:
虚继承的内存分布
#include<iostream>
using namespace std;
class A
{
public:
A(int data = 0) :ma(data) { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
private:
int ma;
};
class B :virtual public A
{
public:
B(int data = 0) :A(data), mb(data) { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
private:
int mb;
};
class C :virtual public A
{
public:
C(int data = 0) :A(data), mc(data) { cout << "C()" << endl; }
~C() { cout << "~C()" << endl; }
private:
int mc;
};
class D : public B, public C
{
public:
D(int data = 0) :B(data), C(data), md(data){ cout << "D()" << endl; }
~D() { cout << "~D()" << endl; }
private:
int md;
};
int main()
{
//D d;
//cout << sizeof(B) << endl;
cout << sizeof(D) << endl;
//cout << sizeof(int*) << endl;
return 0;
}
D的内存布局:
B的内存布局:
c的内存布局:
#include<iostream>
using namespace std;
class A
{
public:
A(int data = 0) :ma(data) { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
int ma;
};
class B :virtual public A
{
public:
B(int data = 0) :A(data), mb(data) { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
int mb;
};
class C :virtual public A
{
public:
C(int data = 0) :A(data), mc(data) { cout << "C()" << endl; }
~C() { cout << "~C()" << endl; }
int mc;
};
class D : public B, public C
{
public:
D(int data = 0) :B(data), C(data), md(data){ cout << "D()" << endl; }
~D() { cout << "~D()" << endl; }
int md;
};
int main()
{
D d;
d.ma = 1;
d.mb = 2;
d.mc = 3;
d.md = 4;
cout << sizeof(d) << endl;
return 0;
}
虚基类表
- 虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现。
- 每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)
- vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,就可以找到虚基类成员。
- 虚基类依旧会在子类里面存在拷贝,但是最多存在一份。
虚继承与虚函数对比
相同点:
都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)
不同点:
-
虚基类依旧存在继承类中,占用存储空间;虚函数不占用存储空间。
-
虚基类表存储的是虚基类相对直接继承类的偏移;而虚函数表存储的是虚函数地址。