C++虚函数_虚继承

罗马 -- 意大利

C++虚函数_虚继承

1 说明

通过一个菱形继承的例子,说明大部分的情况。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A {
int a = 10;
public:
virtual void print1() { cout << "A::print1()" << endl; }
virtual void print2() { cout << "A::print2()" << endl; }
virtual void print3() { cout << "A::print3()" << endl; }
};

class B1 : virtual public A {
int x = 11;
public:
virtual void print1() { cout << "B1::print1()" << endl; }
};

class B2 : virtual public A {
int y = 12;
public:
virtual void print2() { cout << "B2::print2()" << endl; }
virtual void print4() { cout << "B2::print4()" << endl; } // 自己定义
};


class C : public B1, public B2 {
int z = 13;
public:
virtual void print1() { cout << "C::print1()" << endl; }
};

调试环境 VS 2019 ,使用VS提供的 cl 工具查看类内存结构;命令如下:

1
2
3
cl filename.cpp /d1reportSingleClassLayoutXXX # XXX 为类名
# 例如 test.cpp 中类 Base 内存分布情况
cl test.cpp /d1reportSingleClassLayoutBase

2 A 类情况

1
2
3
4
5
6
7
8
9
class A {
int a = 10;
public:
void print0() { cout << "A::print0()" << endl; }
virtual void print1() { cout << "A::print1()" << endl; }
virtual void print2() { cout << "A::print2()" << endl; }
virtual void print3() { cout << "A::print3()" << endl; }
};
// sizeof(A) = 8 = 4 + 4

内存布局如下:

数据成员 a 四个字节

虚函数指针 vfptr 四个字节,vfptr 指向 vftable,虚函数表中依次存 A::print1, A::print2, A::print3


3 B1 类情况

1
2
3
4
5
6
class B1 : virtual public A {
int x = 11;
public:
virtual void print1() { cout << "B1::print1()" << endl; }
};
// sizeof(B1) = 16 = 8 + 4 + 4

内存布局如下:

自身数据成员 X 四个字节,继承自 A 数据成员 a 四个字节;(八个字节

虚基指针 vbptr 四个字节,指向 vbtable,虚基类表内存距离 A 数据的偏移,与A相差 8 个字节;

虚函数指针 vfptr 四个字节, 指向 vftable,虚函数表中依次存 B1::print1, A::print2, A::print3注意覆写B1::print1


4 B2 类情况

1
2
3
4
5
6
class B2 : virtual public A {
int y = 12;
public:
virtual void print2() { cout << "B2::print2()" << endl; }
virtual void print4() { cout << "B2::print4()" << endl; } // 自己定义
}; // sizeof(B2) = 20 = 8 + 4 + 8

内存布局如下:

自身数据成员 y 四个字节,继承自 A 数据成员 a 四个字节;(八个字节

虚基指针 vbptr 四个字节,指向 vbtable,虚基类表内存距离 A 数据的偏移,与 A 相差 8 个字节,第一个字段 -4 说明该 vbptr 到 B2 对象首地址的偏移(因为有虚函数指针的存在);

两个虚函数指针:八个字节

  • B2 中 vfptr,原因在于 B2 自己定义了一个新的虚函数 B2::print4
  • A 中 vfptr 指向 vftable,虚函数表中依次存 A::print1, B2::print2, A::print3注意覆写B2::print2

5 C 类情况

1
2
3
4
5
class C : public B1, public B2 {
int z = 13;
public:
virtual void print1() { cout << "C::print1()" << endl; }
}; // sizeof(C) = 32 = 16 + 8 + 8

内存布局:

自身数据成员 z 四个字节,继承自 A 数据成员 a 四个字节,继承自 B1 数据成员 x 四个字节,继承自 B2 数据成员 y 四个字节;(十六个字节

两个虚基指针:八个字节,分别位于 B1,与 B2 部分,分析同上;

两个虚函数指针:八个字节

  • B2 中 vfptr,原因在于 B2 自己定义了一个新的虚函数 B2::print4
  • A 中 vfptr 指向 vftable,虚函数表中依次存 C::print1, B2::print2, A::print3注意覆写C::print1, B2::print2

6 补充

一模一样的例子,但是不用虚继承!!!

内存分布如下:(上图为无虚继承,下图为有虚继承

  • A 无任何区别

  • B1 少了一个虚基指针,大小少了 4 字节;

  • B2 少了一个虚基指针,一个虚函数指针,B2 自己定义的虚函数,直接加载虚函数表第四项,而不是像虚继承那样重新生成一个虚函数指针(主要是B2中的print4不是共用的)。故大小少了 8 字节。

C 中表象如下:少了两个虚基指针,多了一个重复的 a 数据成员,所以大小少了 4 字节;

C 对象内部的 B1 与 B2 部分来自于继承关系,并且对于 B1 与 B2 部分中的虚函数 print1 进行了覆写。



----------- 本文结束 -----------




0%