C++菱形继承的探索
参考文章:
1、https://blog.csdn.net/haoel/article/details/3081328
2、https://blog.csdn.net/castle_kao/article/details/71024411
前言
C++虚继承下菱形继承的对象内存布局依赖于编译器。对于vs而言,虚继承通过虚基类表和虚基类指针(vbptr)实现;对于g++而言,可能会有另一种实现方式。本文基于文章【1】和文章【2】中描述的实验方法,在Linux和g++编译器环境下,测定了虚继承菱形继承生成对象的内存布局。
实验一:
运行环境:
ubuntu16.04,g++5.4.0,64位
代码:
#include <iostream>
using namespace std;
class B
{
public:
int ib;
char cb;
public:
B() :ib(0), cb('B') {}
virtual void f() { cout << "B::f()" << endl; }
virtual void Bf() { cout << "B::Bf()" << endl; }
};
class B1 : virtual public B
{
public:
int ib1;
char cb1;
public:
B1() :ib1(11), cb1('1') {}
virtual void f() { cout << "B1::f()" << endl; }
virtual void f1() { cout << "B1::f1()" << endl; }
virtual void Bf1() { cout << "B1::Bf1()" << endl; }
};
class B2 : virtual public B
{
public:
int ib2;
char cb2;
public:
B2() :ib2(12), cb2('2') {}
virtual void f() { cout << "B2::f()" << endl; }
virtual void f2() { cout << "B2::f2()" << endl; }
virtual void Bf2() { cout << "B2::Bf2()" << endl; }
};
class D : public B1, public B2
{
public:
int id;
char cd;
public:
D() :id(100), cd('D') {}
virtual void f() { cout << "D::f()" << endl; }
virtual void f1() { cout << "D::f1()" << endl; }
virtual void f2() { cout << "D::f2()" << endl; }
virtual void Df() { cout << "D::Df()" << endl; }
};
int main(void)
{
typedef void(*F)();
D d;
B1 *p = &d; //0
long *op = reinterpret_cast<long*>(p); //1
long *vfptr = reinterpret_cast<long*>(*op);//2
F fun = reinterpret_cast<F>(*(vfptr + 0)); //3
fun();
cout << "sizeof(T): " << sizeof(D) << endl;
return 0;
}
程序运行结果:
D::f()
sizeof(T): 56
实验步骤:
1、依次将代码中【3】处的vfptr偏移值+1,可测出vfptr指向的虚函数表的所有函数信息;
2、变换【0】处指针p的类型,改为D、B1、B2和B,重复1步骤,可测得所有的虚函数表中函数信息。
结论:
上述实验中,D对象内存模型为:
经过内存补齐计算,在64位环境中D对象内存位56byte,与上述内存布局吻合。
1、派生类对象创建过程中,首先创建直接基类(非virtual继承)的对象模型。直接基类对象创建过程可不考虑派生类因素,按照其自身的创建方法创建,依次向上。
2、创建虚基类时候,同样不用考虑派生类,按照自身原本的方法创建。此部分内存放在派生类对象内存最后。
3、创建派生类,类中的虚函数更新到基类的虚函数表中。新添加的部分放在第一个直接基类虚函数表最后,如上述实验。
4、上述实验中,B1* p = &d、B2* p = &d和B* p = &d不相同,但各自指向各自部分的虚函数表首地址。B1* p = &d和D* p = &d相同。
实验二:
代码1:
#include <iostream>
using namespace std;
class A {
int a;
};
class B : virtual public A {
public:
virtual void func() {}
};
int main() {
cout << sizeof(B) << endl;
return 0;
}
不同环境下有不同结果。
实验三:
测试构造函数执行顺序
代码:
#include <iostream>
using namespace std;
class test {
public:
test() {cout << "test" << endl;}
test(int m) {cout << "test" << m << endl;}
};
class A {
public:
A(int) {cout << "A" << endl;}
private:
test t1;
};
class B {
public:
B(int) {cout << "B" << endl;}
private:
test t2;
};
class C :virtual public A, public B {
public:
C(int a) : A(a), B(a) {cout << "C" << endl;}
private:
test t3;
};
int main() {
C a(3);
return 0;
}