一、虚继承
1.问题:多重继承 ==》 派生类对象中内存重复 ==》 访问冲突 内存浪费
2.解决:对出现多份的数据在最近的继承前加viture
3.虚继承时,虚基类指针vbptr指向虚基类表vbtable,虚基类表中存放的就是数据相对于虚基类指针的偏移,从而根据偏移找到数据
vbptr ==> vbtable
vbtable 中:
0 当前最近作用域的偏移 - vbptr的偏移
1 虚继类数据的起始偏移 - vbptr的偏移
二、不同继承下的内存布局 以及构造顺序
虚基类构造优先级大于普通基类
内存布局:先非虚基类,后 虚基类
1.
class A
{
public:
A(int a = 0) :ma(a)
{ std::cout << "A" << std::endl; }
public:
int ma;
};
class B: virtual public A
{
public:
B(int b) :A(b), mb(b){ std::cout << "B" << std::endl; }
public:
int mb;
};
class C : virtua public A
{
public:
C(int c) :A(c), mc(c){ std::cout << "C" << std::endl; }
public:
int mc;
};
class D : public B, public C
{
public:
D(int d) :B(d), C(d),E(d),md(d)
{ std::cout << "D" << std::endl; }
public:
int md;
};
当A为虚基类,虚继承于b.c时:
若为多继承时,内存布局如下:
虚继承的内存布局如下:
class A
{
public:
A(int a = 0) :ma(a){ std::cout << "A" << std::endl; }
public:
int ma;
};
class B: virtual public A
{
public:
B(int b) :A(b), mb(b){ std::cout << "B" << std::endl; }
public:
int mb;
};
class C : viture public A
{
public:
C(int c) :A(c), mc(c){ std::cout << "C" << std::endl; }
public:
int mc;
};
class E
{
public:
E(int e) :me(e){ std::cout << "E" << std::endl; }
public:
int me;
};
class D : public B, public C, viture public E
{
public:
D(int d) :B(d), C(d),E(d),md(d){ std::cout << "D" << std::endl; }
public:
int md;
};
当A为虚基类,虚继承于b.c时且E虚继承D:
若为多继承时,内存布局如下:
虚继承的内存布局如下:
第一个vbptr 存放3个数据,因为指针合并,向距离作用域最近的指针内层合并
class A
{
public:
A(int a = 0) :ma(a){ std::cout << "A" << std::endl; }
public:
int ma;
};
class B: virtual public A
{
public:
B(int b) :A(b), mb(b){ std::cout << "B" << std::endl; }
public:
int mb;
};
class C : public A
{
public:
C(int c) :A(c), mc(c){ std::cout << "C" << std::endl; }
public:
int mc;
};
class E
{
public:
E(int e) :me(e){ std::cout << "E" << std::endl; }
public:
int me;
};
class D : viture public B, public C, public E
{
public:
D(int d) :B(d), C(d),E(d),md(d){ std::cout << "D" << std::endl; }
public:
int md;
};
当A为虚基类,虚继承于b时且B虚继承D
若为多继承时,内存布局如下:
虚继承的内存布局如下:
第一个vbptr 存放3个数据,因为他代替的是B这个虚基类,c中的A没有vbptr,因为C没有虚继承A,只有B虚继承A,所以只有B的A可被替换成vbptr
构造顺序:
先是非虚基类,后虚基类
A->C->A->B->E->D
先构造C,C里有基类A,先构造基类A,后C,同理A,B,最后E.D
析构顺序与构造顺序相反
三、虚函数
编译期间,系统会添加一个隐藏成员vfptr
基类指针引用指向或者引用派生类对象时,一定要将基类的析构写成虚函数
1.虚函数和虚继承的对比
class Base
{
public:
Base(int b) :mb(b){}
void Show(){}
public:
int mb;
};
class Derive : virtual public Base
{
public:
Derive(int d) :md(d), Base(d){}
virtual void Show(){}
public:
int md;
};
多继承内存布局:
虚继承内存布局:
2、成为虚函数的条件:
1.要能取地址
2.依赖对象调用
构造函数 不可以 系统调用 不依赖对象调用
析构函数 可以
内联函数 不可以 不能取地址 函数在调用点直接展开
static函数 不可以 无this指针 不依赖对象调用
3、虚函数表:
第一行:RTTL(run time type information)运行时类型信息
第二行:虚函数指针的偏移
第三行:虚函数的入口地址
虚表的写入时机 构造函数的执行之前