什么是C++对象模型
C++对象模型可以从两个层面来解释:
- (1) C++ 语言中支持的面向对象的程序设计层面,一个类包含构造函数,析构函数,虚函数等等;
- (2) C++ 中所构造的Class的对象的底层实现机制,主要包括类的数据成员,函数成员的内存布局是怎样的,派生类是通过什么机制访问基类成员的等等;
本文主要从第二个方面来进行剖析。
注: C++对象模型的底层实现机制并未标准化,不同的编译器对对象模型的底层实现机制可能有所差异,但其目标都是优化对象空间和时间的存取效率,并支持C++语言说规定的虚函数,虚基类等机制。
加上封装后的布局成本
相比于C, C++在内存布局以及存取时间(效率)上的主要的额外负担(开销)是由virtual 机制引起的,包括:
Virtual Function 机制 : 用于实现派生类与基类之间同名函数的多态机制,以支持一个有效率的“执行期动态绑定”;
Virtual Base Class机制: 用于实现多次出现在继承体系中的Base class有一个单一而被共享的实体;
C++对象模式
C++中数据成员的种类:static 和nonstatic
C++中函数成员的种类:static,nonstatic和virtual
本文讲义以下面的Class Point为例来探究C++对象模型的内存布局:
class Point{
public:
Point(float xval);
virtual ~Point();
float x() const;
static int PointCount();
protected:
virtual ostream & print(ostream & os) const;
float _x;
static int _point_count;
}
上面的Class Point中个data member和function member 将会有怎样的内存布局,将会表现出怎样的联系?
简单对象模型
在简单对象模型中,一个object是由一系列的slots组成,每个slot指向一个members(data member or function member) 。 Members按照类中的声明顺序各被指定一个slot。简单对象模型如下图所示:
在这个简单模型中,object中只存着指向members的指针(slot中存放),这么做可以避免“member有不同的类型,因而需要不同的存储空间所招致的问题”。这样的简单模型中,一个class object的内存大小很容易计算出来:指针大小*Class中声明的member数目。这种简单模型需要较大的内存空间和执行期效率的开销,在实际C++编译器中并不会使用,但是该模型中利用slot存放指向成员的指针的观念在C++中应用广泛。
表格驱动对象模型
为了对所有classes的所有objects都有一致的表达方式,另一种对象模型是把所有与members相关的信息抽象出来,放在一个data member table 和一个function member tale中,class object本身则只包含指向这两个table 的指针。function member table是一系列的的slots ,每一个slot指向一个member function;data member table则直接包含data本身。表格驱动对象模型如下图所示:
基本C++对象模型
基本C++对象模型中(无派生类):
- Nonstatic data members 位于一个class object之内;
- Static data members则位于所有class object之外;
- Static 和 nonstatic function members都位于class object之外;
- Virtual function则通过下面的两个步骤(虚函数表vtble+虚指针vptr)支持:
(1)每个class 都会生成一堆指向virtual function的指针,每个指针都指向Class的一个虚函数,这些指针存放在一个table中,该table称为虚函数表( virtual function table,vtbl),简称vtble。
(2)编译器会为class object生成一个虚函数表指针vptr,指向该类的vtble。vptr在对象内存布局中的位置由编译器决定,一般放在对象的最前端,也有编译器将vptr放在所有显式声明的成员之后。vptr的设定和重置都由class的constructor,destructor和copy assignment运算符自动完成。每一个class所关联的 type_info object (用于支持runtime type identification,RTTI)也经由vtble被给出,通常放在vtble的第一个slot处。RTTI(Run Time Type Identification,运行时类型识别)是为多态机制而生成的信息,包括对象继承关系,对象本身的描述等,只有具有虚函数的对象才会 有type_info object
下图说明了C++对象模型是如何用于 Point Class身上的。这个模型的主要优点在于它的空间和存取时间的效率 ; 主要缺点在于如果应用程序代码本身未曾改变,但是所用到的class objects的nonstatic data members有所修改(增加,删除或改动),那么这些应用程序代码需要重新编译。
为了后续方便讲述在单继承和多继承情况下的C++对象模型,在这里构造出一个较为通用的基类Base,它包含了 static 和nonstatic的类别数据成员,static,nonstatic和virtual类别的函数成员。
#include <iostream>
#include <typeinfo>
using namespace std;
class Base{
public:
Base(int v):base_value(v){}
int getBaseValue(){return base_value;}
static void counter(){}
virtual ~Base(){cout<<"Base::~Base()";}
virtual void print(){cout<<"Base::print()";}
private:
int base_value;
static int base_count;
};
void testBase(Base & p){
cout<<"The start memory address of object: "<<(int*)(&p)<<endl;
cout<<endl;
cout<<"Type information: "<<endl;
const type_info& t1 = typeid(p);
const char* class_name=t1.name();
cout<<"Class name: "<<class_name<<endl;
cout<<"The memory address of vtble: "<< (int*)(*((int*)(&p)))<<endl;
//the pointer to type_info is saved in slot 0 of vtble
cout<<"The first function memory address of vtble: " << (int*)(*(((int*)*((int*)(&p))+1)))<<endl;
cout<<"The destructor memory address: " <<(int*)(*(((int*)*((int*)(&p))+1)))<<endl;
cout<<endl;
cout<<"The print() function memory address : "<<(int*)(*(((int*)*((int*)(&p))+2)))<<endl;
//call print() function by address
typedef void(*fun)(void);
fun printFunc=(fun) (int*)(*(((int*)*((int*)(&p))+2)));
cout<<"Call virtual function print(): ";
printFunc(); //if address is correct, p.print() will be called
cout<<endl;
cout<<endl;
//verify static function member
p.counter();
cout<<"The address of static function counter(): "<<(int*)(&Base::counter)<<endl;
cout<<endl;
//verify nonstatic function member
cout<<"Speculate the address of data member base_value: " << (int*)(&p)+1 <<endl;
cout << "Output the base_value(or data) at this address: " << *((int*)(&p)+1) << endl;
cout << "Base::getbase_value(): " << p.getBaseValue() << endl;
}
int main(){
Base b(100);
testBase(b);
return 0;
}
运行结果:
Base类的对象模型如下图所示: