在Project Properties->C++->Command Line->Additional Options里面加上/d1reportSingleClassLayoutDerived
编译时,可输出类Derived的内存布局。
非虚拟继承:
在派生类对象里,按照继承声明顺序依次分布基类对象,最后是派生类数据成员。
若基类声明了虚函数,则基类对象头部有一个虚函数表指针,然后是基类数据成员。
在基类虚函数表中,依次是基类的虚函数,若某个函数被派生类override,则替换为派生类的函数。
派生类独有的虚函数被加在第一个基类的虚函数表后面。
虚拟继承:
在派生类对象里,按照继承声明顺序依次分布非虚基类对象,然后是派生类数据成员,最后是虚基类对象。
若基类声明了虚函数,则基类对象头部有一个虚函数表指针,然后是基类数据成员。
在基类虚函数表中,依次是基类的虚函数,若某个函数被派生类override,则替换为派生类的函数。
若直接从虚基类派生的类没有非虚父类,且声明了新的虚函数,则该派生类有自己的虚函数表,在该派生类头部;否则派生类独有的虚函数被加在第一个非虚基类的虚函数表后面。
直接从虚基类派生的类内部还有一个虚基类表指针,在数据成员之前,非虚基类对象之后(若有的话)。
虚基类表中第一个值是该派生类起始地址到该表的偏移;之后的值依次是该派生类的虚基类到该表位置的地址偏移。
非虚拟单继承
{
virtual void Func2() {cout << " Base " << endl;}
int iBase;
} ;
class Derived : public Base
{
public :
void Func2() {cout << " Derived Func2 called " << endl;} ;
void Func3() {cout << " Derived Func3 called " << endl;} ;
virtual void Func5() {cout << " Derived Func5 called " << endl;} ;
int iDerived;
} ;
1>class Derived size(12):
1> +---
1> | +--- (base class Base)
1> 0 | | {vfptr}
1> 4 | | iBase
1> | +---
1> 8 | iDerived
1> +---
1>Derived::$vftable@:
1> | &Derived_meta
1> | 0
1> 0 | &Derived::Func2
1> 1 | &Derived::Func5
非虚拟多继承
{
virtual void Func2() {cout << " Base " << endl;}
int iBase;
} ;
class Base1
{
public :
virtual void Func1() {cout << " Base1 Func1 called " << endl;} ;
virtual void Func2() {cout << " Base1 Func2 called " << endl;} ;
virtual void Func3() {cout << " Base1 Func3 called " << endl;} ;
int iBase1;
} ;
class Base2
{
public :
virtual void Func2() {cout << " Base2 Func2 called " << endl;} ;
virtual void Func3() {cout << " Base2 Func3 called " << endl;} ;
virtual void Func4() {cout << " Base2 Func4 called " << endl;} ;
int iBase2;
} ;
class Derived : public Base, public Base1, public Base2
{
public :
void Func2() {cout << " Derived Func2 called " << endl;} ;
void Func3() {cout << " Derived Func3 called " << endl;} ;
virtual void Func5() {cout << " Derived Func5 called " << endl;} ;
int iDerived;
} ;
1>class Derived size(28):
1> +---
1> | +--- (base class Base)
1> 0 | | {vfptr}
1> 4 | | iBase
1> | +---
1> | +--- (base class Base1)
1> 8 | | {vfptr}
1>12 | | iBase1
1> | +---
1> | +--- (base class Base2)
1>16 | | {vfptr}
1>20 | | iBase2
1> | +---
1>24 | iDerived
1> +---
1>Derived::$vftable@Base@:
1> | &Derived_meta
1> | 0
1> 0 | &Derived::Func2
1> 1 | &Derived::Func5
1>Derived::$vftable@Base1@:
1> | -8
1> 0 | &Base1::Func1
1> 1 | &thunk: this-=8; goto Derived::Func2
1> 2 | &Derived::Func3
1>Derived::$vftable@Base2@:
1> | -16
1> 0 | &thunk: this-=16; goto Derived::Func2
1> 1 | &thunk: this-=8; goto Derived::Func3
1> 2 | &Base2::Func4
虚拟单继承
{
public :
virtual void Func2() {} ;
int BaseValue;
} ;
class Derived : virtual public Base
{
public :
void Func2() {} ;
virtual void Func4() {} ;
int DerivedValue;
} ;
1>class Derived size(20):
1> +---
1> 0 | {vfptr}
1> 4 | {vbptr}
1> 8 | DerivedValue
1> +---
1> +--- (virtual base Base)
1>12 | {vfptr}
1>16 | BaseValue
1> +---
1>Derived::$vftable@Derived@:
1> | &Derived_meta
1> | 0
1> 0 | &Derived::Func4
1>Derived::$vbtable@:
1> 0 | -4
1> 1 | 8 (Derivedd(Derived+4)Base)
1>Derived::$vftable@Base@:
1> | -12
1> 0 | &Derived::Func2
虚拟多继承
{
virtual void Func2() {cout << " Base " << endl;}
int iBase;
} ;
class Base1
{
public :
virtual void Func1() {cout << " Base1 Func1 called " << endl;} ;
virtual void Func2() {cout << " Base1 Func2 called " << endl;} ;
virtual void Func3() {cout << " Base1 Func3 called " << endl;} ;
int iBase1;
} ;
class Base2
{
public :
virtual void Func2() {cout << " Base2 Func2 called " << endl;} ;
virtual void Func3() {cout << " Base2 Func3 called " << endl;} ;
virtual void Func4() {cout << " Base2 Func4 called " << endl;} ;
int iBase2;
} ;
class Derived : public virtual Base, public virtual Base1, public Base2
{
public :
void Func2() {cout << " Derived Func2 called " << endl;} ;
void Func3() {cout << " Derived Func3 called " << endl;} ;
virtual void Func5() {cout << " Derived Func5 called " << endl;} ;
int iDerived;
} ;
1>class Derived size(32):
1> +---
1> | +--- (base class Base2)
1> 0 | | {vfptr}
1> 4 | | iBase2
1> | +---
1> 8 | {vbptr}
1>12 | iDerived
1> +---
1> +--- (virtual base Base)
1>16 | {vfptr}
1>20 | iBase
1> +---
1> +--- (virtual base Base1)
1>24 | {vfptr}
1>28 | iBase1
1> +---
1>Derived::$vftable@Base2@:
1> | &Derived_meta
1> | 0
1> 0 | &Derived::Func2
1> 1 | &Derived::Func3
1> 2 | &Base2::Func4
1> 3 | &Derived::Func5
1>Derived::$vbtable@:
1> 0 | -8
1> 1 | 8 (Derivedd(Derived+8)Base)
1> 2 | 16 (Derivedd(Derived+8)Base1)
1>Derived::$vftable@Base@:
1> | -16
1> 0 | &thunk: this-=16; goto Derived::Func2
1>Derived::$vftable@Base1@:
1> | -24
1> 0 | &Base1::Func1
1> 1 | &thunk: this-=24; goto Derived::Func2
1> 2 | &thunk: this-=24; goto Derived::Func3
菱形虚拟继承
using namespace std;
class Top
{
public :
virtual void Func1() {} ;
int TopValue;
} ;
class Left: virtual public Top
{
public :
void Func1() {} ;
virtual void Func2() {}
int LeftValue;
} ;
class Right: virtual public Top
{
public :
void Func1() {} ;
int RightValue;
} ;
class Derived : public Left, public Right
{
public :
void Func1() {} ;
void Func3() {}
virtual void Func4() {} ;
int DerivedValue;
} ;
int main()
{
Derived d;
Derived* pd = &d;
Top* pt = &d;
cout<<( int *)pt<<endl;
cout<<( int *)pd<<endl;
return 0 ;
}
1>class Derived size(32):
1> +---
1> | +--- (base class Left)
1> 0 | | {vfptr}
1> 4 | | {vbptr}
1> 8 | | LeftValue
1> | +---
1> | +--- (base class Right)
1>12 | | {vbptr}
1>16 | | RightValue
1> | +---
1>20 | DerivedValue
1> +---
1> +--- (virtual base Top)
1>24 | {vfptr}
1>28 | TopValue
1> +---
1>Derived::$vftable@Left@:
1> | &Derived_meta
1> | 0
1> 0 | &Left::Func2
1> 1 | &Derived::Func4
1>Derived::$vbtable@Left@:
1> 0 | -4
1> 1 | 20 (Derivedd(Left+4)Top)
1>Derived::$vbtable@Right@:
1> 0 | 0
1> 1 | 12 (Derivedd(Right+0)Top)
1>Derived::$vftable@Top@:
1> | -24
1> 0 | &Derived::Func1
如上代码的输出结果表示虚基类指针的地址比派生类指针地址多了24,符合上面列出的对象内存布局。
当通过对象访问基类成员时,无论是否为虚拟继承,都可以再编译时期获得对象内存布局,直接计算出偏移量访问。
当通过指针访问基类成员时,如果是非虚拟继承,直接计算偏移量访问;
如果是虚拟继承,需要先计算出虚基类表指针的偏移地址,再根据指针找到虚基类表,取虚基类表中该项的内容,根据此内容计算虚基类对象的偏移地址,再根据成员在虚基类对象中的偏移地址访问。
所以一般说来,当从派生类中访问虚基类成员时,应该先强制转化派生类指针为虚基类指针,然后一直使用虚基类指针来访问虚基类成员变量。这样做,可以避免每次都要计算虚基类地址的开销。
虚函数表不一定在派生类对象的首地址,例如当派生类从基类虚拟继承时,派生类如果没有定义新的虚函数,那么在派生类的对象空间中,首先分布的是虚基类表,然后是派生类成员,最后是虚基类对象,虚函数表在虚基类对象开始位置。
{
public :
virtual void fun() {}
} ;
class Derived: virtual public Base
{
void fun() {}
int i;
} ;
1>class Derived size(12):
1> +---
1> 0 | {vbptr}
1> 4 | i
1> +---
1> +--- (virtual base Base)
1> 8 | {vfptr}
1> +---
1>Derived::$vbtable@:
1> 0 | 0
1> 1 | 8 (Derivedd(Derived+0)Base)
1>Derived::$vftable@:
1> | -8
1> 0 | &Derived::fun