转载请注明出处http://blog.youkuaiyun.com/a827461712/article/details/22726449
感谢在博客写作过程中韩朋程和罗国佳的宝贵意见!!
总述
继承,是C++语言程序设计中重要的组成部分。C++对象模型,简单的理解可以说就是C++中一个对象的内存布局。下面就总结一下类继承对C++对象的内存布局产生的影响。
首先,请记住如下规则(以下规则针对的都是VC编译器):
1、 对于一般的类继承。如果基类和派生类都具有虚函数,在派生类对象的内存布局中,所有的虚函数都存放在一张虚函数表中(多重继承时会有多个虚函数表)。即,派生类中,从基类继承的虚函数与自身新定义的虚函数共用一张虚函数表 。
2、 对于虚继承。如果基类和派生类都具有虚函数,在派生类对象的内存布局中,基类和派生类的虚函数存在于不同的虚函数表中。即,派生类中,从基类继承的虚函数在一张虚函数表中,派生类自身新定义的虚函数在另一张虚函数表中。为了支持虚继承,VC编译器在对象模型中加入了一个虚基类指针,虚基类指针在任何情况下都不会共享。
3、派生类对象的内存布局中要保证基类子对象空间的完整性。
下面对这三条规则进行详细说明。
1.一般继承、不带虚函数
一般来说,不带虚函数的类继承是没有什么实质性意义的。但是,为了更好的说明本文的主题。对这种情况进行简单的描述。
假如有如下两个类:
class Base
{
int n1;
};
class Derived:public Base
{
int n2;
};
定义一个派生类对象b,b的内存布局如下图所示。其中,n1和n2各占4B:
上文举例使用的是单一继承,对于多重继承,内存布局并没有大的改变。还是先放基类成员再放派生类成员,基类成员的排列顺序按照定义派生类时基类的排列顺序来定。
完整代码如下:
#include <iostream>
using namespace std;
class Base
{
public:
Base(const int n = 3):n1(n){}
int n1;
};
class Derived:public Base
{
public:
Derived(const int nlr = 2):n2(nlr){}
int n2;
};
int main()
{
cout<<"********Write by a827461712*********"<<endl;
Derived d;
cout<<"Size of d is:"<<sizeof(d)<<endl;
int nTest1 = (int)*((int*)&d);
cout<<nTest1<<endl;
int nTest2 = (int)*((int*)&d+1);
cout<<nTest2<<endl;
return 0;
}
运行结果如下:
2. 一般继承、带虚函数
class Base
{
public:
Base(const int n = 3):n1(n){}
~Base(){}
virtual void fun(){cout<<"Base fun called"<<endl;}
int n1;
};
class Derived:public Base
{
public:
Derived(const int nlr = 2):n2(nlr){}
~Derived(){}
virtual void myfun(){cout<<"Derived fun called"<<endl;}
int n2;
};
根据代码可知,定义一个派生类对象d,此时d的内存布局如下图所示:
#include <iostream>
using namespace std;
class Base
{
public:
Base(const int n = 3):n1(n){}
~Base(){}
virtual void fun(){cout<<"Base fun called"<<endl;}
int n1;
};
class Derived:public Base
{
public:
Derived(const int nlr = 2):n2(nlr){}
~Derived(){}
virtual void myfun(){cout<<"Derived fun called"<<endl;}
int n2;
};
typedef void(*Fun)(void);
int main()
{
cout<<"********Write by a827461712*********"<<endl;
Derived d;
cout<<"Size of d is:"<<sizeof(d)<<endl;
Fun fun = NULL;
fun = (Fun)*((int*)*(int*)(&d));
fun();
fun = (Fun)*((int*)*(int*)(&d)+1);
fun();
int nTest1 = (int)*((int*)&d+1);
cout<<nTest1<<endl;
int nTest2 = (int)*((int*)&d+2);
cout<<nTest2<<endl;
return 0;
}
class Base1
{
public:
Base1(const int n = 3):nBase1(n){}
~Base1(){}
virtual void fun(){cout<<"Base1 fun called"<<endl;}
int nBase1;
};
class Base2
{
public:
Base2(const int n = 3):nBase2(n){}
~Base2(){}
virtual void fun(){cout<<"Base2 fun called"<<endl;}
int nBase2;
};
class Derived:public Base1,public Base2
{
public:
Derived(const int nlr = 2):n(nlr){}
~Derived(){}
virtual void myfun(){cout<<"Derived fun called"<<endl;}
int n;
};
定义一个派生类对象,其内存布局如图所示。由于派生类Derived定义时,Base1位置在Base2前面,因此,派生类对象内存中,Base1子对象在Base2子对象之前:
#include <iostream>
using namespace std;
class Base1
{
public:
Base1(const int n = 3):nBase1(n){}
~Base1(){}
virtual void fun(){cout<<"Base1 fun called"<<endl;}
int nBase1;
};
class Base2
{
public:
Base2(const int n = 4):nBase2(n){}
~Base2(){}
virtual void fun(){cout<<"Base2 fun called"<<endl;}
int nBase2;
};
class Derived:public Base1,public Base2
{
public:
Derived(const int nlr = 2):n(nlr){}
~Derived(){}
virtual void myfun(){cout<<"Derived fun called"<<endl;}
int n;
};
typedef void(*Fun)(void);
int main()
{
cout<<"********Write by a827461712*********"<<endl;
Derived d;
cout<<"Size of d is:"<<sizeof(d)<<endl;
Fun fun = NULL;
fun = (Fun)*((int*)*(int*)(&d));//Base1::fun()
fun();
fun = (Fun)*((int*)*(int*)(&d)+1);//Derived::myfun()
fun();
int nTest1 = (int)*((int*)&d+1);//Base1::nBase1
cout<<nTest1<<endl;
fun = (Fun)*((int*)*((int*)(&d)+2));//Base2::fun()
fun();
int nTest2 = (int)*((int*)&d+3);//Base2::nBase2
cout<<nTest2<<endl;
int nTest3 = (int)*((int*)&d+4);//Derived::n
cout<<nTest3<<endl;
return 0;
}
运行结果如图所示,下图中打印的信息与上文中给出的内存布局完全符合:
3. 虚继承
虚继承是为了在类似于“菱形继承”的情况下,在派生类中只保留基类的一份副本。请记住另一条规则“当碰到虚继承的时候,任何规则都变得不一样!”。为了实现虚继承,编译器需要在派生类中加入一个虚基类指针。而且,如果虚基类和派生类都定义有各自的虚函数,此时,派生类和虚基类中的虚函数并不会共享一个虚函数表。这与一般继承时有巨大区别。
此处以最典型的“菱形继承”为例进行讲解。
假设有如下4个类,Left类和Right类虚继承自基类Base,派生类Derived又多重继承自Left和Right。
class Base
{
public:
Base(const int n = 3):nBase(n){}
~Base(){}
virtual void fun(){cout<<"Base fun called"<<endl;}
int nBase;
};
class Left:virtual public Base
{
public:
Left(const int n = 4):nLeft(n){}
~Left(){}
virtual void leftfun(){cout<<"Left fun called"<<endl;}
int nLeft;
};
class Right:virtual public Base
{
public:
Right(const int n = 5):nRight(n){}
~Right(){}
virtual void rightfun(){cout<<"Right fun called"<<endl;}
int nRight;
};
class Derived:public Left,public Right
{
public:
Derived(const int nlr = 2):n(nlr){}
~Derived(){}
virtual void myfun(){cout<<"Derived fun called"<<endl;}
int n;
};
类继承关系如下图所示:
内存布局如图所示:
从这个内存布局图可知,在虚继承中,虚基类子对象放在对象的最低端。但是对于一般的继承,基类子对象都是放在派生类对象的开始处。上图中,前12字节是类Left的子对象,接下来12字节是Right的子对象,接下来4字节是Derived的空间,最后8字节虚基类子对象空间。
在虚继承中,以上文中的例子来说,假如类Left重写了虚基类中的fun函数。这时,Left中的fun函数会覆盖虚基类虚函数表中的fun函数。更重要的是,Left中重写的那个fun并不会出现在类Left的虚函数表中(此时Left中有两个虚函数表,一个放虚基类中的虚函数,另一个放派生类新定义的虚函数)。
完整代码如下:
#include <iostream>
using namespace std;
class Base
{
public:
Base(const int n = 3):nBase(n){}
~Base(){}
virtual void fun(){cout<<"Base fun called"<<endl;}
int nBase;
};
class Left:virtual public Base
{
public:
Left(const int n = 4):nLeft(n){}
~Left(){}
virtual void leftfun(){cout<<"Left fun called"<<endl;}
int nLeft;
};
class Right:virtual public Base
{
public:
Right(const int n = 5):nRight(n){}
~Right(){}
virtual void rightfun(){cout<<"Right fun called"<<endl;}
int nRight;
};
class Derived:public Left,public Right
{
public:
Derived(const int nlr = 2):n(nlr){}
~Derived(){}
virtual void myfun(){cout<<"Derived fun called"<<endl;}
int n;
};
typedef void(*Fun)(void);
int main()
{
cout<<"********Write by a827461712*********"<<endl;
Derived d;
cout<<"Size of d is:"<<sizeof(d)<<endl;
Fun fun = NULL;
fun = (Fun)*((int*)*(int*)(&d));//Left::leftfun() Left::vptr
fun();
fun = (Fun)*((int*)*(int*)(&d)+1);//Derived::myfun()
fun();
int nTest1 = (int)*((int*)&d+2);//Left::nLeft
cout<<nTest1<<endl;
fun = (Fun)*((int*)*((int*)(&d)+3));//Right::rightfun() Right::vptr
fun();
int nTest2 = (int)*((int*)&d+5);//Right::nRight
cout<<nTest2<<endl;
int nTest3 = (int)*((int*)&d+6);//Derived::n
cout<<nTest3<<endl;
fun = (Fun)*((int*)*((int*)(&d)+7));//Base::fun() Base::vptr
fun();
int nTest4 = (int)*((int*)&d+8);//Base::nBase
cout<<nTest4<<endl;
return 0;
}
运行结果如图所示: