C++对象模型可以概括为以下2部分:
在C++类中有两种数据成员、三种成员函数:
代码举例说明:
//base.h
#pragma once
#include
using namespace std;
class Base
{
public:
Base(int);
virtual ~Base(void);
int getIBase() const;
static int instanceCount();
virtual void print() const;
protected:
int iBase;
static int count;
};
问:Base类在机器中我们如何构建出各种成员数据和成员函数的呢?
在介绍C++使用的对象模型之前,介绍2种对象模型:简单对象模型(a simple object model)、表格驱动对象模型(a table-driven object model)。
所有的成员占用相同的空间(跟成员类型无关),对象只是维护了一个包含成员指针的一个表。表中放的是成员的地址,无论上成员变量还是函数,都是这样处理。对象并没有直接保存成员而是保存了成员的指针。
这个模型在 简单对象模型 的基础上又添加了 一个间接层。
将成员分成函数和数据,并且用两个表格保存,然后对象只保存了 两个指向表格的指针。
表格驱动对象模型 可以保证所有的 对象 具有相同的大小(只保存俩指针); 简单对象模型 的类对象还与成员的个数相关。
在C++对象模型中:
C++对象模型优点与缺点:
class Base
{
public:
Base(int i) :baseI(i){};
virtual void print(void){ cout << "调用了虚函数Base::print()"; }
virtual void setI(){cout<<"调用了虚函数Base::setI()";}
virtual ~Base(){}
private:
int baseI;
};
当一个类本身定义了虚函数,或其父类有虚函数时,为了支持多态机制,编译器将为该类添加一个虚函数指针(vptr)。
虚函数指针一般都放在 对象内存布局的第一个位置 上,这是为了保证在多层继承或多重继承的情况下能以最高效率取到虚函数表。
当vptr位于对象内存最前面时,对象的地址即为虚函数指针地址。我们可以取得虚函数指针的地址:
Base b(1000);
int *vptrAddr = (int *)(&b); //强行把类对象的地址转换为 int* 类型,取得了虚函数指针的地址。
虚函数指针指向虚函数表,虚函数表中存储的是一系列虚函数的地址,虚函数地址出现的顺序与类中虚函数声明的顺序一致。
对虚函数指针地址值,可以得到虚函数表的地址,也即是虚函数表第一个虚函数的地址:
typedef void(*Fun)(void);
Fun vfunc = (Fun)*( (int *)*(int*)(&b));
/*
取出虚函数表指针的值: *(int*)(&b); 它是一个地址,虚函数表的地址
把虚函数表的地址强制转换成 int* :(int *)*(int*)(&b);
再把它转化成我们Fun指针类型 : (Fun)*(int*)*(int*)(&b)
*/
cout << "第一个虚函数的地址是:" << (int *)*(int*)(&b) << endl;
cout << "通过地址,调用虚函数Base::print():";
vfunc();
class Base
{
public:
Base(int i) :baseI(i){};
virtual void print(void){ cout << "调用了虚函数Base::print()"; }
//virtual void setI(){cout<<"调用了虚函数Base::setI()";}
virtual ~Base(){}
private:
int baseI;
};
class Derive : public Base
{
public:
Derive(int d) :Base(1000), DeriveI(d){};
//override父类虚函数
virtual void print(void){ cout << "Drive::Drive_print()" ; }
// Derive声明的新的虚函数
virtual void Drive_print(){ cout << "Drive::Drive_print()" ; }
virtual ~Derive(){}
private:
int DeriveI;
};
简单继承下有重写的C++对象模型: 无重写的话,在子类虚函数表中是不会覆盖父类虚函数的。
继承类图为:
从单继承可以知道,派生类中只是扩充了基类的虚函数表。如果是多继承的话,又是如何扩充的?
class Base
{
public:
Base(int i) :baseI(i){};
virtual ~Base(){}
int getI(){ return baseI; }
static void countI(){};
virtual void print(void){ cout << "Base::print()"; }
private:
int baseI;
static int baseS;
};
class Base_2
{
public:
Base_2(int i) :base2I(i){};
virtual ~Base_2(){}
int getI(){ return base2I; }
static void countI(){};
virtual void print(void){ cout << "Base_2::print()"; }
private:
int base2I;
static int base2S;
};
class Drive_multyBase :public Base, public Base_2
{
public:
Drive_multyBase(int d) :Base(1000), Base_2(2000) ,Drive_multyBaseI(d){};
virtual void print(void){ cout << "Drive_multyBase::print" ; }
virtual void Drive_print(){ cout << "Drive_multyBase::Drive_print" ; }
private:
int Drive_multyBaseI;
};
D类对象内存布局中:
图中青色表示b1类子对象实例,黄色表示b2类子对象实例,灰色表示D类子对象实例。
由于D类间接继承了B类两次,导致D类对象中含有两个B类的数据成员ib
,一个属于来源B1类,一个来源B2类。这样不仅增大了空间,更重要的是引起了程序歧义:
D d;
d.ib = 1; //二义错误,调用的是B1::ib还是B2::ib?
//正确调用方式
d.B1::ib = 1;
d.B2::ib = 1;
什么是虚继承?
//类的内容与前面相同
class B{...}
class B1 : virtual public B
虚继承是为了解决 重复继承中多个间接父类 的问题的,所以不能使用上面简单的扩充并为每个虚基类提供一个虚函数指针(这样会导致重复继承的基类会有多个虚函数表)形式。
虚继承的派生类的内存结构,和普通继承完全不同:
总结:
类图如下:
子类对象模型如下:
类图如下:
菱形虚拟继承下,最派生类D类的对象模型又有不同的构成了。在D类对象的内存构成上,有以下几点:
菱形虚拟继承下的C++对象模型为: