继承、虚继承、虚函数内存分布(MSVC下)

前提知识:

对象的内存中只包含成员变量,存储在栈区或堆区(使用 new 创建对象);

成员函数与对象内存分离,存储在代码区

对象的大小,可以自己分析,int 四个字节,指针也是四个字节。(在x86中)可用sizeof运算符查看对象的大小。

1、普通继承 

代码示例

class A {
public:
	int ma;
	void func(){
		cout << "A :: func" << endl;
	}
};

class B : public A {
public:
	int mb;
	void func() {
		cout << "B :: func" << endl;
	}
};

内存分布

class A size(4):
        +---
 0      | ma
        +---

class B size(8):
        +---
 0      | +--- (base class A)
 0      | | ma
        | +---
 4      | mb
        +---

 2、普通多继承

代码示例

        派生类都只有一个基类,称为单继承。C++ 也支持多继承,即一个派生类可以有两个或多个基类。

class A {
public:
	int ma;
	void func(){
		cout << "A :: func" << endl;
	}
};

class B : public A {
public:
	int mb;
	void func() {
		cout << "B :: func" << endl;
	}
};

class C : public A {
public:
	int mc;
	void func() {
		cout << "C :: func" << endl;
	}
};

class D : public B, public C {
public:
	int md;
	void func() {
		cout << "D :: func" << endl;
	}
};

 内存分布

        从多继承的内存分布中,我们可以看到存在一些缺点:在一个派生类中保留间接基类的多份同名成员;菱形继承中,会有命名冲突的出现

        类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量继承到类 D 中变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->D 这条路径。此时,在D中对A中的变量赋值,就会有命名冲突。并且在使用基类A的公开成员函数时,也会指向不明确报错。

class A size(4):
        +---
 0      | ma
        +---

class B size(8):
        +---
 0      | +--- (base class A)
 0      | | ma
        | +---
 4      | mb
        +---

class C size(8):
        +---
 0      | +--- (base class A)
 0      | | ma
        | +---
 4      | mc
        +---

class D size(20):
        +---
 0      | +--- (base class B)
 0      | | +--- (base class A)
 0      | | | ma
        | | +---
 4      | | mb
        | +---
 8      | +--- (base class C)
 8      | | +--- (base class A)
 8      | | | ma
        | | +---
12      | | mc
        | +---
16      | md
        +---

 3、虚继承

        为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。

        在继承方式前面加上 virtual 关键字就是虚继承;

        虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类,本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。

 代码示例

class A {
public:
	int ma;
	void func(){
		cout << "A :: func" << endl;
	}
};

class B :virtual public A {
public:
	int mb;
	void func() {
		cout << "B :: func" << endl;
	}
};

class C : virtual public A {
public:
	int mc;
	void func() {
		cout << "C :: func" << endl;
	}
};

class D : public B, public C {
public:
	int md;
	void func() {
		cout << "D :: func" << endl;
	}
};

 内存分布

在MSVC编译器中,引入虚基类表,如果某个派生类有一个或者多个虚基类,编译器会在派生类对象中安插一个指针(vbptr)指向虚基类表(vbtable)。虚基类表放的是偏移量。

class A size(4):
        +---
 0      | ma
        +---



class B size(12):
        +---
 0      | {vbptr}
 4      | mb
        +---
        +--- (virtual base A)
 8      | ma
        +---

B::$vbtable@:
 0      | 0
 1      | 8 (Bd(B+0)A)



class C size(12):
        +---
 0      | {vbptr}
 4      | mc
        +---
        +--- (virtual base A)
 8      | ma
        +---

C::$vbtable@:
 0      | 0
 1      | 8 (Cd(C+0)A)



class D size(24):
        +---
 0      | +--- (base class B)
 0      | | {vbptr}
 4      | | mb
        | +---
 8      | +--- (base class C)
 8      | | {vbptr}
12      | | mc
        | +---
16      | md
        +---
        +--- (virtual base A)
20      | ma
        +---

D::$vbtable@B@:
 0      | 0
 1      | 20 (Dd(B+0)A)

D::$vbtable@C@:
 0      | 0
 1      | 12 (Dd(C+0)A)

 4、虚函数

        如果一个类包含了虚函数,那么在创建该类的对象时就会额外地增加一个数组,数组中的每一个元素都是虚函数的入口地址。每定义的任何一个对象,都会有vfptr,vfptr指向的是同一块vftable。

        子类继承了父类之后,同名函数(包括返回值,函数名,参数列表)也会自动是virtual类型。子类的vftable中的函数,只有 virtual类型,不是virtual类型的,不会在vftable中。

代码示例

class A {
public:
	int ma;
	virtual void func() {
		cout << "A" << endl;
	}
};
class B : public A {
public:
	int mb;
	void func() {
		cout << "B" << endl;
	}
	void func2() {
		cout << "B" << endl;
	}
	virtual void func3() {

	}
};

内存分布

class A size(8):
        +---
 0      | {vfptr}
 4      | ma
        +---

A::$vftable@:
        | &A_meta
        |  0
 0      | &A::func


class B size(12):
        +---
 0      | +--- (base class A)
 0      | | {vfptr}
 4      | | ma
        | +---
 8      | mb
        +---

B::$vftable@:
        | &B_meta
        |  0
 0      | &B::func
 1      | &B::func3

5、虚继承(多)、虚函数

代码示例

class A {
public:
	int ma;
	virtual void func(){
		cout << "A :: func" << endl;
	}
};

class B :virtual public A {
public:
	int mb;
	void func() {
		cout << "B :: func" << endl;
	}
};

class C : virtual public A {
public:
	int mc;
	void func() {
		cout << "C :: func" << endl;
	}
};

class D : public B, public C {
public:
	int md;
	void func() {
		cout << "D :: func" << endl;
	}
};

 内存分布

class A size(8):
        +---
 0      | {vfptr}
 4      | ma
        +---

A::$vftable@:
        | &A_meta
        |  0
 0      | &A::func


class B size(16):
        +---
 0      | {vbptr}
 4      | mb
        +---
        +--- (virtual base A)
 8      | {vfptr}
12      | ma
        +---

B::$vbtable@:
 0      | 0
 1      | 8 (Bd(B+0)A)

B::$vftable@:
        | -8
 0      | &B::func

class C size(16):
        +---
 0      | {vbptr}
 4      | mc
        +---
        +--- (virtual base A)
 8      | {vfptr}
12      | ma
        +---

C::$vbtable@:
 0      | 0
 1      | 8 (Cd(C+0)A)

C::$vftable@:
        | -8
 0      | &C::func


class D size(28):
        +---
 0      | +--- (base class B)
 0      | | {vbptr}
 4      | | mb
        | +---
 8      | +--- (base class C)
 8      | | {vbptr}
12      | | mc
        | +---
16      | md
        +---
        +--- (virtual base A)
20      | {vfptr}
24      | ma
        +---

D::$vbtable@B@:
 0      | 0
 1      | 20 (Dd(B+0)A)

D::$vbtable@C@:
 0      | 0
 1      | 12 (Dd(C+0)A)

D::$vftable@:
        | -20
 0      | &D::func

 6、多态的指向

在多态中,基类指针指向派生类对象时候,永远指向的是派生类基类部分数据的起始地址

父类并不能访问子类中在父类未出现的函数和变量。只能访问虚函数表中的函数。

7、vbptr 与 vfptr 优先问题

1、只有 虚继承。

 2、基类有虚函数

 3、子类有新的虚函数

 

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值