以下测试都是基于VS,X86环境(32bit)。。
注意:虚基指针指向虚基类,虚函数指针指向虚表。。
Linux与vs的唯一区别是,在Linux下虚函数指针与虚基指针合并了
项目->(右键)属性->配置属性->C/C+±>命令行
/d1 reportSingleClassLayoutXXX 或者/d1 reportAllClassLayout
测试一、虚继承与继承的区别
// 1. 多了一个虚基指针,虚基指针指向虚基表,虚基表中存放相对于虚基指针,虚函数指针的偏移信息,用于查找虚函数指针的位置。
// 2. 虚基类子对象位于派生类存储空间的最末尾(先存不变的后存共享的)
#pragma vtordisp(off)
#include <iostream>
using std::cout;
using std::endl;
class A
{
public:
A() : _ia(10) {}
virtual
void f()
{
cout << "A::f()" << endl;
}
private:
int _ia;
};
class B
: virtual public A
{
public:
B() : _ib(20) {}
void fb()
{
cout << "A::fb()" << endl;
}
virtual
void f()
{
cout << "B::f()" << endl;
}
virtual
void fb2()
{
cout << "B::fb2()" << endl;
}
private:
int _ib;
};
int main(void)
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
B b;
return 0;
}
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单
// 入门提示:
// 1. 使用解决方案资源管理器窗口添加/管理文件
// 2. 使用团队资源管理器窗口连接到源代码管理
// 3. 使用输出窗口查看生成输出和其他消息
// 4. 使用错误列表窗口查看错误
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
根据对应的描述,添加virtual 关键字
1.1、单个继承,不带虚函数
4/8
1>class B size(8):
1> ±–
1> 0 | ±-- (base class A)
1> 0 | | _ia
1> | ±–
1> 4 | _ib
1.2、单个虚继承,不带虚函数
4/12
1>class B size(12):
1> ±–
1> 0 | {vbptr} -------> 虚基指针
1> 4 | _ib //先存自己的再存基类的
1> ±–
1> ±-- (virtual base A)
1> 8 | _ia
1> ±–
1>B::$vbtable@:---------->虚基表
1> 0 | 0
1> 1 | 8 (Bd(B+0)A)
测试二:单个虚继承,带虚函数
// 1.如果派生类没有自己新增的虚函数,此时派生类对象不会产生虚函数指针
// 2.如果派生类拥有自己新增的虚函数,此时派生类对象就会产生自己本身的
// 虚函数指针(指向新增的虚函数),并且该虚函数指针位于派生类对象存储空间的开始位置
2.1、单个继承,带虚函数
8/12
1>class B size(12):
1> ±–
1> 0 | ±-- (base class A)
1> 0 | | {vfptr}---->虚函数指针
1> 4 | | _ia
1> | ±–
1> 8 | _ib
1> ±–
1>B::$vftable@:---->虚函数表
1> | &B_meta
1> | 0
1> 0 | &B::f====>B重写了基类f函数,若没有重写则是&A::f
2.2、单个继承,带虚函数(自己新增虚函数)
8/12
class B size(12):
1> ±–
1> 0 | ±-- (base class A)
1> 0 | | {vfptr}---->虚函数指针,B继承自A,新增的虚函数没必要在开始位置重新生成vfptr,因为继承过来的vfptr就在开始位置,所以直接在后面加新增的虚函数的入口地址
1> 4 | | _ia
1> | ±–
1> 8 | _ib
1> ±–
1>B::$vftable@:
1> | &B_meta
1> | 0
1> 0 | &B::f====>B重写了基类发f函数,若没有重写则是&A::f
1> 1 | &B::fb2====>B新增的虚函数,产生自己本身的虚函数指针(指向新增的虚函数),并且该虚函数指针位于派生类对象存储空间的开始位置
总结:针对2.1、2.2,普通继承,派生类新增虚函数直接放在基类虚表中;且基类布局在前面
2.3、单个虚继承,带虚函数
// 虚基类指针vbptr指向虚基类表vbtable,虚基类表中存放的就是数据(vfptr)相对于虚基类指针的偏移,从而根据偏移找到数据(vfptr),从而找到虚函数入口地址
8/16
1>class B size(16):
1> ±–
1> 0 | {vbptr} //有虚继承的时候就多一个虚基指针,虚基指针指向虚基表
1> 4 | _ib
1> ±–
1> ±-- (virtual base A)
1> 8 | {vfptr}//有虚函数的时候就产生一个虚函数指针,虚函数指针指向虚函数表
1>12 | _ia
1> ±–
1>B::
v
b
t
a
b
l
e
@
:
1
>
0
∣
01
>
1
∣
8
(
B
d
(
B
+
0
)
A
)
1
>
B
:
:
vbtable@: 1> 0 | 0 1> 1 | 8 (Bd(B+0)A) 1>B::
vbtable@:1>0∣01>1∣8(Bd(B+0)A)1>B::vftable@:
1> | -8//代表vftable距离内存开始位置的距离
1> 0 | &B::f
2.4、单个虚继承,带虚函数(自己新增虚函数)
8/20
1>class B size(20):
1> ±–
1> 0 | {vfptr}---->派生类新增的虚函数指针
1> 4 | {vbptr}---->虚基指针
1> 8 | _ib
1> ±–
1> ±-- (virtual base A)
1>12 | {vfptr}---->基类的虚函数指针
1>16 | _ia
1> ±–
1>B::KaTeX parse error: Expected 'EOF', got '&' at position 18: …table@B@: 1> | &̲B_meta 1> | 0 …vbtable@:
1> 0 | -4//往前偏移4字节到派生类新增的虚函数指针
1> 1 | 8 (Bd(B+4)A)往后偏移8字节到派生类重写基类的虚函数
1>B::$vftable@A@:
1> | -12往前偏移12字节到派生类新增的虚函数指针
1> 0 | &B::f
总结:2.3、2.4、虚继承多一个虚基指针,如果派生类新增虚函数,则放在最前面;且基类布局放在最后面
// 测试三:多重继承(带虚函数)
// 1、每个基类都有自己的虚函数表
// 2、派生类如果有自己新增的虚函数,会被加入到第一个虚函数表之中
// 3、内存布局中,其基类的布局按照基类被继承时的顺序进行排列
// 4、派生类会覆盖基类的虚函数,只有第一个虚函数表中存放的是真实的被覆盖的虚函数的地址;
// 其它的虚函数表中存放的并不是真实的对应的虚函数的地址,而只是一条跳转指令
#pragma vtordisp(off)
#include <iostream>
using std::cout;
using std::endl;
class Base1
{
public:
Base1() : _iBase1(10) {}
virtual void f()
{
cout << "Base1::f()" << endl;
}
virtual void g()
{
cout << "Base1::g()" << endl;
}
virtual void h()
{
cout << "Base1::h()" << endl;
}
private:
int _iBase1;
};
class Base2
{
public:
Base2() : _iBase2(100) {}
virtual void f()
{
cout << "Base2::f()" << endl;
}
virtual void g()
{
cout << "Base2::g()" << endl;
}
virtual void h()
{
cout << "Base2::h()" << endl;
}
private:
int _iBase2;
};
class Base3
{
public:
Base3() : _iBase3(1000) {}
virtual void f()
{
cout << "Base3::f()" << endl;
}
virtual void g()
{
cout << "Base3::g()" << endl;
}
virtual void h()
{
cout << "Base3::h()" << endl;
}
private:
int _iBase3;
};
class Derived
: virtual public Base1
, /*virtual*/ public Base2
, /*virtual*/ public Base3
{
public:
Derived() : _iDerived(10000) {}
void f()
{
cout << "Derived::f()" << endl;
}
virtual void g1()
{
cout << "Derived::g1()" << endl;
}
private:
int _iDerived;
};
int main(void)
{
Derived d;
Base2* pBase2 = &d;
Base3* pBase3 = &d;
Derived* pDerived = &d;
pBase2->f();
cout << "sizeof(d) = " << sizeof(d) << endl;
cout << "&Derived = " << &d << endl; // 这三个地址值是不一样的
cout << "pBase2 = " << pBase2 << endl; //
cout << "pBase3 = " << pBase3 << endl; //
return 0;
}
3.1、普通多重继承,带虚函数,自己有新增虚函数
28
1>class Derived size(28):
1> ±–
1> 0 | ±-- (base class Base1)
1> 0 | | {vfptr}
1> 4 | | _iBase1
1> | ±–
1> 8 | ±-- (base class Base2)
1> 8 | | {vfptr}
1>12 | | _iBase2
1> | ±–
1>16 | ±-- (base class Base3)
1>16 | | {vfptr}
1>20 | | _iBase3
1> | ±–
1>24 | _iDerived
1> ±–
1>Derived::KaTeX parse error: Expected 'EOF', got '&' at position 22: …e@Base1@: 1> | &̲Derived_meta 1>…vftable@Base2@:
1> | -8
1> 0 | &thunk: this-=8; goto Derived::f //虚函数表还可以存放跳转指令
1> 1 | &Base2::g
1> 2 | &Base2::h
1>Derived::$vftable@Base3@:
1> | -16
1> 0 | &thunk: this-=16; goto Derived::f
1> 1 | &Base3::g
1> 2 | &Base3::h
3.2、虚拟多重继承,带虚函数,自己有新增虚函数(只有第一个是虚继承)
32
1>class Derived size(32):
1> ±–
1> 0 | ±-- (base class Base2)
1> 0 | | {vfptr}
1> 4 | | _iBase2
1> | ±–
1> 8 | ±-- (base class Base3)
1> 8 | | {vfptr}
1>12 | | _iBase3
1> | ±–
1>16 | {vbptr}
1>20 | _iDerived
1> ±–
1> ±-- (virtual base Base1)
1>24 | {vfptr}
1>28 | _iBase1
1> ±–
1>Derived::KaTeX parse error: Expected 'EOF', got '&' at position 22: …e@Base2@: 1> | &̲Derived_meta 1>…vftable@Base3@:
1> | -8
1> 0 | &thunk: this-=8; goto Derived::f
1> 1 | &Base3::g
1> 2 | &Base3::h
1>Derived::
v
b
t
a
b
l
e
@
:
1
>
0
∣
−
161
>
1
∣
8
(
D
e
r
i
v
e
d
d
(
D
e
r
i
v
e
d
+
16
)
B
a
s
e
1
)
1
>
D
e
r
i
v
e
d
:
:
vbtable@: 1> 0 | -16 1> 1 | 8 (Derivedd(Derived+16)Base1) 1>Derived::
vbtable@:1>0∣−161>1∣8(Derivedd(Derived+16)Base1)1>Derived::vftable@Base1@:
1> | -24
1> 0 | &thunk: this-=24; goto Derived::f
1> 1 | &Base1::g
1> 2 | &Base1::h
3.3、虚拟多重继承,带虚函数,自己有新增虚函数(三个都是虚继承)
36
1>class Derived size(36):
1> ±–
1> 0 | {vfptr} //以空间换时间
1> 4 | {vbptr}
1> 8 | _iDerived
1> ±–
1> ±-- (virtual base Base1)
1>12 | {vfptr}
1>16 | _iBase1
1> ±–
1> ±-- (virtual base Base2)
1>20 | {vfptr}
1>24 | _iBase2
1> ±–
1> ±-- (virtual base Base3)
1>28 | {vfptr}
1>32 | _iBase3
1> ±–
1>Derived::KaTeX parse error: Expected 'EOF', got '&' at position 24: …Derived@: 1> | &̲Derived_meta 1>…vbtable@:
1> 0 | -4
1> 1 | 8 (Derivedd(Derived+4)Base1)
1> 2 | 16 (Derivedd(Derived+4)Base2)
1> 3 | 24 (Derivedd(Derived+4)Base3)
1>Derived::KaTeX parse error: Expected 'EOF', got '&' at position 33: …> | -12 1> 0 | &̲Derived::f 1> 1…vftable@Base2@:
1> | -20
1> 0 | &thunk: this-=8; goto Derived::f
1> 1 | &Base2::g
1> 2 | &Base2::h
1>Derived::$vftable@Base3@:
1> | -28
1> 0 | &thunk: this-=16; goto Derived::f
1> 1 | &Base3::g
1> 2 | &Base3::h
// 测试四:菱形虚继承
//虚基指针所指向的虚基表的内容:
// 1. 虚基指针的第一条内容表示的是该虚基指针距离所在的子对象的首地址的偏移
// 2. 虚基指针的第二条内容表示的是该虚基指针距离虚基类子对象的首地址的偏移
// 测试四:钻石型虚继承
#pragma vtordisp(off)
#include <iostream>
using std::cout;
using std::endl;
class B
{
public:
B() : _ib(10), _cb('B') {}
virtual void f()
{
cout << "B::f()" << endl;
}
virtual void Bf()
{
cout << "B::Bf()" << endl;
}
private:
int _ib;
char _cb;
};
class B1 : virtual public B
{
public:
B1() : _ib1(100), _cb1('1') {}
virtual void f()
{
cout << "B1::f()" << endl;
}
#if 1
virtual void f1()
{
cout << "B1::f1()" << endl;
}
virtual void Bf1()
{
cout << "B1::Bf1()" << endl;
}
#endif
private:
int _ib1;
char _cb1;
};
class B2 : virtual public B
{
public:
B2() : _ib2(1000), _cb2('2') {}
virtual void f()
{
cout << "B2::f()" << endl;
}
#if 1
virtual void f2()
{
cout << "B2::f2()" << endl;
}
virtual void Bf2()
{
cout << "B2::Bf2()" << endl;
}
#endif
private:
int _ib2;
char _cb2;
};
class D : public B1, public B2
{
public:
D() : _id(10000), _cd('3') {}
virtual void f()
{
cout << "D::f()" << endl;
}
#if 1
virtual void f1()
{
cout << "D::f1()" << endl;
}
virtual void f2()
{
cout << "D::f2()" << endl;
}
virtual void Df()
{
cout << "D::Df()" << endl;
}
#endif
private:
int _id;
char _cd;
};
int main(void)
{
D d;
cout << sizeof(d) << endl;
return 0;
}
4.1、菱形普通继承(存储二义性)
48
class D size(48):
1> ±–
1> 0 | ±-- (base class B1)
1> 0 | | ±-- (base class B)
1> 0 | | | {vfptr}
1> 4 | | | _ib
1> 8 | | | _cb //1
1> | | | (size=3) //内存对齐
1> | | ±–
1>12 | | _ib1
1>16 | | _cb1
1> | | (size=3)
1> | ±–
1>20 | ±-- (base class B2)
1>20 | | ±-- (base class B)
1>20 | | | {vfptr}
1>24 | | | _ib
1>28 | | | _cb
1> | | | (size=3)
1> | | ±–
1>32 | | _ib2
1>36 | | _cb2
1> | | (size=3)
1> | ±–
1>40 | _id
1>44 | _cd
1> | (size=3)
1> ±–
1>D::KaTeX parse error: Expected 'EOF', got '&' at position 19: …able@B1@: 1> | &̲D_meta 1> | 0 …vftable@B2@:
1> | -20
1> 0 | &thunk: this-=20; goto D::f
1> 1 | &B::Bf//继承自B
1> 2 | &D::f2//D重写
1> 3 | &B2::Bf2//B2新增
4.2、菱形虚拟继承
52
1>class D size(52):
1> ±–
1> 0 | ±-- (base class B1)
1> 0 | | {vfptr}
1> 4 | | {vbptr}
1> 8 | | _ib1
1>12 | | _cb1
1> | | (size=3)
1> | ±–
1>16 | ±-- (base class B2)
1>16 | | {vfptr}
1>20 | | {vbptr}
1>24 | | _ib2
1>28 | | _cb2
1> | | (size=3)
1> | ±–
1>32 | _id
1>36 | _cd
1> | (size=3)
1> ±–
1> ±-- (virtual base B)
1>40 | {vfptr}
1>44 | _ib
1>48 | _cb
1> | (size=3)
1> ±–
1>D::KaTeX parse error: Expected 'EOF', got '&' at position 19: …able@B1@: 1> | &̲D_meta 1> | 0 …vftable@B2@:
1> | -16
1> 0 | &D::f2//D重写
1> 1 | &B2::Bf2//D新增
1>D::
v
b
t
a
b
l
e
@
B
1
@
:
1
>
0
∣
−
4
/
/
B
1
自
己
的
v
f
p
t
r
1
>
1
∣
36
(
D
d
(
B
1
+
4
)
B
)
/
/
偏
移
到
B
的
v
f
p
t
r
1
>
D
:
:
vbtable@B1@: 1> 0 | -4//B1自己的vfptr 1> 1 | 36 (Dd(B1+4)B)//偏移到B的vfptr 1>D::
vbtable@B1@:1>0∣−4//B1自己的vfptr1>1∣36(Dd(B1+4)B)//偏移到B的vfptr1>D::vbtable@B2@:
1> 0 | -4//B2自己的vfptr
1> 1 | 20 (Dd(B2+4)B)//偏移到B的vfptr
1>D::$vftable@B@:
1> | -40
1> 0 | &D::f//D重写
1> 1 | &B::Bf