前言
前文C++虚函数调用过程深度理解(二)笔者探讨验证了一些关于C++虚函数调用过程
概念的深度理解,希望能帮助你更了解cpp
或激起你探索cpp
的兴趣,那便是笔者的更新动力。
笔者将探讨验证关于含有虚基类的类构造顺序
。
结论
基本概念:
msvc
指microsoft visual c++
编译器,应用于windows
平台。gcc
指gnu c compiler
,g++
是其cpp
版本,一般应用于类unix
即linux
平台。clang
也是一种编译器,本文没有使用到。
结论:
- 类的构造顺序是一致的,不管是在
gcc
编译器还是msvc
编译器。- 笔者认为是
cpp标准
制定了这方面的规则。
- 笔者认为是
- 虽然从结果上看是一致的,但实现
细节
还是有差异。虚基表
实现有差异,gcc
实现的类布局相对紧凑
即对象携带的虚基表指针
更少,即占用内存更少。- 但关于对象
虚基表指针
详细实现,笔者还没找到详细资料,在微软的c++
参考手册上略有提及,但未深入涉及实现细节。
- 但关于对象
验证
本次验证使用如下示例代码:
#include <iostream>
#include <string>
class A {
public:
A(const std::string& s = std::string()) {
std::cout << "A()" << " " << s << std::endl;
}
~A() {
std::cout << "~A()" << std::endl;
}
};
class B {
public:
B() {
std::cout << "B()" << std::endl;
}
~B() {
std::cout << "~B()" << std::endl;
}
};
class C : public virtual A, public virtual B {
public:
C() {
std::cout << "C()" << " : public virtual A, public virtual B" << std::endl;
}
~C() {
std::cout << "~C()" << std::endl;
}
};
class D : public virtual A, public virtual B {
public:
D() {
std::cout << "D()" << " : public virtual A, public virtual B" << std::endl;
}
~D() {
std::cout << "~D()" << std::endl;
}
};
class E : public C, public D, virtual public B {
public:
E() {
std::cout << "E() : public C, public D, virtual public B" << std::endl;
}
~E() {
std::cout << "~E()" << std::endl;
}
};
class AB :
public
virtual //当注释这行时,在DCB中,A实例有两份,一份是public引入, 一份是public virtual引入
A {
public:
AB() : A("called_from_AB()") {
std::cout << "AB() : public virtual A" << std::endl;
}
~AB() {
std::cout << "~AB()" << std::endl;
}
};
class AC : virtual public A {
public:
AC() : A("called_from_AC()") {
std::cout << "AC() : virtual public A" << std::endl;
}
~AC() {
std::cout << "~AC()" << std::endl;
}
};
class DCB : public AB, public AC {
public:
DCB() :
AC::A("called_from_DCB()"), //msvc gcc
//AB::A("called_from_DCB"), //msvc gcc
//A("called_from_DCB"), //gcc 上这样写可以编译通过;msvc这样写编译不通过
AB(), AC() {
std::cout << "DCB() : : public AB, public AC" << std::endl;
}
~DCB() {
std::cout << "~DCB()" << std::endl;
}
};
class F {
public:
F() {
std::cout << "F()" << std::endl;
}
~F() {
std::cout << "~F()" << std::endl;
}
};
class G : public E, virtual public F {
public:
G() : E(), F() {
std::cout << "G() : public E, virtual public F" << std::endl;
}
~G() {
std::cout << "~G()" << std::endl;
}
};
class G_NoVirutalF : public E, public F {
public:
G_NoVirutalF() : E(), F() {
std::cout << "G_NoVirutalF() : public E, public F" << std::endl;
}
~G_NoVirutalF() {
std::cout << "~G_NoVirutalF()" << std::endl;
}
};
int main() {
A a;
std::cout << "sizeof A = " << sizeof(a) << std::endl;
B b;
std::cout << "sizeof B = " << sizeof(b) << std::endl;
C c;
std::cout << "sizeof C = " << sizeof(c) << std::endl;
D d;
std::cout << "sizeof D = " << sizeof(d) << std::endl;
E e;
std::cout << "sizeof E = " << sizeof(e) << std::endl;
G g;
std::cout << "sizeof G = " << sizeof(g) << std::endl;
G_NoVirutalF g_novf;
std::cout << "sizeof G_NoVirutalF = " << sizeof(g_novf) << std::endl;
DCB dcb;
std::cout << "sizeof DCB = " << sizeof(dcb) << std::endl;
return 0;
}
virtual
可在public
前后,如virtual public
和public virtual
。
执行结果差异如下:
从上图可以看出:
msvc
和gcc
实现差异导致对象大小不同。
还有个额外的知识点:
空类对象的大小为1字节
,编译器为了将对象区分默认做的设置。
绘制的继承关系图及构造顺序图如下:
如何确定含虚基类的类构造顺序
,笔者根据实际验证情况及个人理解,有如下推断过程:
A
B
C = [A, B]
D = [A, B]
E = [C, D, B] = [[[A, B], C], [[A, B], D], [B]]
= [A, B, [C], [[A, B], D], [B]]
= [A, B, C, [[A, B], D], [B]]
# 解D中的[A, B],因为A, B是虚基类,且列表最前面已有A, B虚基类,则这里去除A, B
= [A, B, C, [D], [B]]
# 解D
= [A, B, C, D, [B]]
# 解[B],因为[B]为vitrual public即虚基类,且列表前面已有B,则这里消去[B]
= [A, B, C, D]
E = [A, B, C, D]
F
G = [E, F] = [[[C, D, B], E], F]
= [[[[A, B], C], [[A, B], D], [B]], F]
#这部分过程与上面推断E的过程一致,可得到
= [A, B, C, D, [E], [F]]
# 解[E]
= [A, B, C, D, E, [F]]
# 因为F为virtual public即虚基类,则在列表中检查是否存储虚基类F。F不存在,则将其加入到前面虚基类部分[A, B]的尾部
= [A, B, F, C, D, E]
# 这部分与G推断过程不同的是最后一步F的推断
G_NoVirutalF= [E, F]
= [[[[A, B], C], [[A, B], D], [B]], F]
#这部分过程与上面推断E的过程一致,可得到
= [A, B, C, D, [E], [F]]
# 解[E]
= [A, B, C, D, E, [F]]
# 因为F为public,不是虚基类,所以维持它的位置
= [A, B, C, D, E, F]
根据上述推断,可得到大致的推断规则(也适用于不含虚基类的类构造顺序
):
- 先将
类继承关系
按继承顺序
逐级展开- 如
E
继承于C, D, B
=>E = [C, D, B]
- 其中
C = [A, B]
和D = [A, B]
,A
和B
为最底层基类则展开结束。代入使用[[A, B], C]
和[[A, B], D]
- =>
E = [[[A, B], C], [[A, B], D] , [B]]
- =>
- 将
[[A, B], C]
中的[A, B]
展开,=>E = [A, B, [C], [[A, B], D], [B]]
, 其中A, B
为虚基类 - 展开
C
=>E = [A, B, C, [[A, B], D], [B]]
- 展开
[[A, B], D]
,因为D
虚继承于A, B
,故A
B
为虚基类,又因为A
B
已存在,故这里消除。若不是虚继承,则保留。- =>
E = [A, B, C, [D], [B]]
- =>
- 因为
D
为非虚继承,前面的C
也是同理,这里保留。- =>
E = [A, B, C, D, [B]]
- 假如这里最后面的
[B]
为[X]
且为虚继承,则需要将X
移动到前面虚基类部分[A, B]
后面得到E = [A, B, F, C, D]
。
- =>
- 因为后面的
B
为虚继承,所以需要检查前面是否存在虚基类B。因已存在,故这里消除。- =>
E = [A, B, C, D]
- =>
- 加上
E
部分,则其最后构造顺序为[A, B, C, D, E]
。
- 如
- 后面
虚基类
移动至列表前面虚基类
最后,按继承相对顺序
。
结尾
-
行文至此,相关内容已分享结束。当然关于
gcc
及msvc
对于虚基表
实现有详细描述的较正式文档可在评论区留言,我去看看。- 关于这块及相关详细信息可能会在后续更新。
-
如果觉得还不错,你的
点赞关注加收藏
便是笔者继续更新
的最大动力哦^_^
。