瞅瞅C++中的对象模型(中)
前言
上一篇讲了C++对象模型中的一部分,这里接着总结。
1.4 一般继承无虚函数覆盖
这里说的情况主要是子类继承了父类的虚函数,但没有改写父类的虚函数。 继承关系图如下:
实验代码如下:
Base 类和前一个相同。
//Derive类:
class Derive:public Base{
public:
Derive(int val = 0):d_data(val){
cout<<"constructor in Derive\n";
}
//以下三个是测试的虚函数
virtual void f1() { cout << "virtual Derive::f1" << endl; }
virtual void g1() { cout << "virtual Derive::g1" << endl; }
virtual void h1() { cout << "virtual Derive::h1" << endl; }
void testInDerive(){
cout<<"show data member in derive: "<<d_data<<endl;
}
int d_data;
};
mian函数:
#include<iostream>
using namespace std;
#define MAXSIZE 100
#include "derive1.h"
typedef void(*Fun)();
int main()
{
Base b(100),b1(1000);
Derive d(200);
cout<<"the size of d(Derive):"<<sizeof(d)<<endl;
Fun pFun = NULL;
// cout << "virtual pointer's address :" << *(int*)(&d) << endl;
// cout << "the address of first virtual function is:" << (int*)*(int*)(&d) << endl;
cout<<"\nvirtual function test.....\n";
cout<<"\n for b(Base)\n";
// pFun = (Fun)*((int*)*(int*)(&b));
// pFun();
pFun = (Fun)*((int*)*(int*)(&b)+0); // Base::f()
pFun();
pFun = (Fun)*((int*)*(int*)(&b)+1); // Base::g()
pFun();
pFun = (Fun)*((int*)*(int*)(&b)+2); // Base::h()
pFun();
cout<<"\n for d(derive):\n";
// pFun = (Fun)*((int*)*(int*)(&d));
// pFun();
pFun = (Fun)*((int*)*(int*)(&d)+0); // Base::f()
pFun();
pFun = (Fun)*((int*)*(int*)(&d)+1); // Base::g()
pFun();
pFun = (Fun)*((int*)*(int*)(&d)+2); // Base::h()
pFun();
pFun = (Fun)*((int*)*(int*)(&d)+3); // Derive::f1()
pFun();
pFun = (Fun)*((int*)*(int*)(&d)+4); // Derive::g1()
pFun();
pFun = (Fun)*((int*)*(int*)(&d)+5); // Derive::h1()
pFun();
cout<<"\ndata member test...\n";
int *pdata = (int*)((int*)(&d)+1); //虚函数加4个字节即是第一个数据成员的地址
cout<<"b_data:"<<*pdata<<endl;
pdata = (int*)((int*)(&d)+2);
cout<<"d_data"<<*pdata<<endl;
int c;
cin>>c;
return 0;
}
结果如下:
同样的,这里我想说以下几点:
(1)、对于有虚函数的继承来说,子类会共用父类的vptr槽位,也就是说子类不会再增加一个针对子类的vptr(只针对这种情况,多重继承和虚继承在此不考虑)。子类的虚函数添加在继承父类的虚函数表中。
(2)、子类数据成员添加在继承自父类的数据成员之后。
基本示意图如下:
1.5 一般继承有虚函数覆盖
前面算是有了一点多态的模子,但是还没有真正实现多态。如果要实现多态,一般来说需要在子类中改写(overwrite)父类的同名函数。 ok,那么如果子类有改写父类的虚函数,其内存模型又是怎么样的呢?
en,稍微修改一下实验代码,使其继承结构如下所示:
修改Derive类中的函数如下:
class Derive:public Base{
public:
Derive(int val = 0):d_data(val){
cout<<"constructor in Derive\n";
}
//以下三个是测试的虚函数
virtual void f1() { cout << "virtual Derive::f1" << endl; }
//重写父类的g函数
virtual void g() { cout << "virtual Derive::g1" << endl; }
//重写父类的h函数
virtual void h() { cout << "virtual Derive::h1" << endl; }
void testInDerive(){
cout<<"show data member in derive: "<<d_data<<endl;
}
int d_data;
};
也要稍微改下main函数:
#include<iostream>
using namespace std;
#define MAXSIZE 100
#include "derive1.h"
typedef void(*Fun)();
int main()
{
Base b(100),b1(1000);
Derive d(200);
cout<<"the size of d(Derive):"<<sizeof(d)<<endl;
Fun pFun = NULL;
// cout << "virtual pointer's address :" << *(int*)(&d) << endl;
// cout << "the address of first virtual function is:" << (int*)*(int*)(&d) << endl;
cout<<"\nvirtual function test.....\n";
cout<<"\n for b(Base)\n";
// pFun = (Fun)*((int*)*(int*)(&b));
// pFun();
pFun = (Fun)*((int*)*(int*)(&b)+0); // Base::f()
pFun();
pFun = (Fun)*((int*)*(int*)(&b)+1); // Base::g()
pFun();
pFun = (Fun)*((int*)*(int*)(&b)+2); // Base::h()
pFun();
cout<<"\n for d(derive):\n";
// pFun = (Fun)*((int*)*(int*)(&d));
// pFun();
pFun = (Fun)*((int*)*(int*)(&d)+0); // Base::f()
pFun();
pFun = (Fun)*((int*)*(int*)(&d)+1); // Derive::g()
pFun();
pFun = (Fun)*((int*)*(int*)(&d)+2); // Derive::h()
pFun();
pFun = (Fun)*((int*)*(int*)(&d)+3); // Derive::f1()
pFun();
// pFun = (Fun)*((int*)*(int*)(&d)+4); // Derive::g1()
// pFun();
//
// pFun = (Fun)*((int*)*(int*)(&d)+5); // Derive::h1()
// pFun();
cout<<"\ndata member test...\n";
int *pdata = (int*)((int*)(&d)+1); //虚函数加4个字节即是第一个数据成员的地址
cout<<"b_data:"<<*pdata<<endl;
pdata = (int*)((int*)(&d)+2);
cout<<"d_data"<<*pdata<<endl;
return 0;
}
最终的实验结果如下图所示:
这个实验主要就想说一点,即子类的同名函数会在虚函数表中覆盖掉父类的虚函数地址。即像下图这样:
okok,重点来了,这和多态有什么关系?
通过,这种“悄无声息”(其实背后是编译器帮我们实现了替换)的替换,我们就可以用父类的指针或者引用指向整个继承体系中的不同对象,在实际调用的时候,虽然函数名字相同,但是实际调用的函数已经不同了。
如下图所示。
如果bp指向的真实对象是b,那么bp->g(),调用的就是Base 类的虚函数; 相反,如果bp指向的是d,那么bp->g(),最终调用的就是Derive 里的虚函数g()。 由此达到了同样的代码根据不同的实际对象调用不同的函数,也就是实现了多态(确切的说是多态的一种)。
上面将的基本上是C++中的内存模型的重点和难点了。 接下来的部分算是补充,因为说实话,多重继承和虚继承在工程实践中用到的并不是非常多(就我目前了解的来说),从某种程度上来说算是个“鸡肋”, 但是出于完整性考虑,下面也就一并总结了。
1.6 多重继承无虚函数覆盖
多重继承是C++支持的特性之一,即一个子类可以有多个父类,可以继承多个父类的数据成员和成员函数(当然,虚函数也可以被继承下来)。为了支持多重继承下的多态,C++中的内存模型设计了子类可以继承父类的多个虚表指针(从某种程度上来说也就相当于继承了父类的多个虚表) 。
下面就来小探究一波。
设计一个继承类图,如下。
参考【2】,设计了实验代码,如下。
Base1类
class Base1 {
public:
Base1(int val = 10):b1_data(val){ cout<<"constructor in base1\n";} //构造函数
//以下三个是测试的虚函数
virtual void f() { cout << "virtual Base1::f" << endl; }
virtual void g() { cout << "virtual Base1::g" << endl; }
virtual void h1() { cout << "virtual Base1::h1" << endl; }
void show(){cout<<"Base1:: show"<<endl;} //普通的成员函数
int b1_data; //数据成员
};
Base2类
class Base2 {
public:
Base2(int val = 20):b2_data(val){ cout<<"constructor in base2\n";} //构造函数
//以下三个是测试的虚函数
virtual void f() { cout << "virtual Base2::f" << endl; }
virtual void g() { cout << "virtual Base2::g" << endl; }
virtual void h2() { cout << "virtual Base2::h2" << endl; }
void show(){cout<<"Base2:: show"<<endl;} //普通的成员函数
int b2_data; //数据成员
};
Base3类:
class Base3 {
public:
Base3(int val = 30):b3_data(val){ cout<<"constructor in base3\n";} //构造函数
//以下三个是测试的虚函数
virtual void f() { cout << "virtual Base3::f" << endl; }
virtual void g() { cout << "virtual Base3::g" << endl; }
virtual void h3() { cout << "virtual Base3::h3" << endl; }
void show(){cout<<"Base3:: show"<<endl;} //普通的成员函数
int b3_data; //数据成员
};
Derive类
class Derive:public Base1,public Base2,public Base3{
public:
Derive(int val = 100):d_data(val){
cout<<"constructor in derive\n";
}
// virtual void f(){cout << "virtual Derive::f" << endl;} //重写子类的虚函数
//
// virtual void h1(){cout << "virtual Derive::h1" << endl;} //重写子类的虚函数
virtual void i(){cout << "virtual Derive::i" << endl;} //增添的虚函数
int d_data; //数据成员
};
main函数:
typedef void(*Fun)();
int main()
{
Base1 b(100),b1(1000);
Derive d(200);
cout<<"the size of d(Derive):"<<sizeof(d)<<endl;
int** pVtab = (int**)&d;
Fun pFun = NULL;
cout<<"\nvirtual function test.....\n";
cout << "[0] Base1:: vptr->" << endl;
pFun = (Fun)pVtab[0][0];
cout << " [0] ";
pFun();
pFun = (Fun)pVtab[0][1];
cout << " [1] ";
pFun();
pFun = (Fun)pVtab[0][2];
cout << " [2] ";
pFun();
pFun = (Fun)pVtab[0][3];
cout << " [3] ";
pFun();
pFun = (Fun)pVtab[0][4];
cout << " [4] ";
cout<<pFun<<endl;
cout << "[1] Base1.b1_data = " << (int)pVtab[1] << endl;
int s = sizeof(Base1)/4;
cout << "\n[" << s << "] Base2::_vptr->"<<endl;
pFun = (Fun)pVtab[s][0];
cout << " [0] ";
pFun();
pFun = (Fun)pVtab[s][1];
cout << " [1] ";
pFun();
pFun = (Fun)pVtab[s][2];
cout << " [2] ";
pFun();
pFun = (Fun)pVtab[s][3];
cout << " [3] ";
cout<<pFun<<endl;
cout << "["<< s+1 <<"] Base2.b2_data = " << (int)pVtab[s+1] << endl;
s = s + sizeof(Base2)/4;
cout << "[" << s << "] Base3::_vptr->"<<endl;
pFun = (Fun)pVtab[s][0];
cout << " [0] ";
pFun();
pFun = (Fun)pVtab[s][1];
cout << " [1] ";
pFun();
pFun = (Fun)pVtab[s][2];
cout << " [2] ";
pFun();
pFun = (Fun)pVtab[s][3];
cout << " [3] ";
cout<<pFun<<endl;
s++;
cout << "["<< s <<"] Base3.b3_data = " << (int)pVtab[s] << endl;
s++;
cout << "["<< s <<"] Derive.d_data = " << (int)pVtab[s] << endl;
return 0;
}
实验结果如下图:
上图说明了以下几点内容:
(1)、红色圈圈表明了,子类会继承父类所有的虚表指针,从某种程度上来说也算是继承了其虚函数表。同时,如果子类有新增的虚函数,则就会添加在第一个继承的虚函数表中。
(2)、蓝色圈圈表明了,就多继承来说,子类会继承父类的虚表指针和其数据成员,并且按照声明的顺序依次排布。
(3)、黄色圈圈表明了,子类继承下来的虚函数表,只有最后一个虚函数表末尾会置空为0.
有点蒙? 来,一图胜千言。
1.7 多重继承有虚函数覆盖
这里的情况就是在上面多重继承的基础上,子类会覆盖父类的虚函数。
根据 参考【2】设计了下面的继承图。
实验代码只需要稍微修改一下Derive类,使其重写一些父类的一些函数如下:
Derive类
class Derive:public Base1,public Base2,public Base3{
public:
Derive(int val = 100):d_data(val){
cout<<"constructor in derive\n";
}
virtual void f(){cout << "virtual Derive::f" << endl;} //重写子类的虚函数
virtual void h1(){cout << "virtual Derive::h1" << endl;} //重写子类的虚函数
virtual void i(){cout << "virtual Derive::i" << endl;} //增添的虚函数
int d_data; //数据成员
};
实验结果如下:
这里就有一点要说明的,子类中重写父类的函数,对应继承下来的虚函数表都需要进行更改。 示意图如下:
en… 没想到这第一部分还没写完,看起来有点多,还得放在下一篇博客来总结虚继承的情况了。
参考
【1】、C++ 虚函数表解析
【2】、C++ 对象的内存布局(上)
【3】、C++ 对象的内存布局(下)