《深度探索C++对象模型》阅读笔记(一)——对象的内存布局1
来源:互联网 发布:家用干洗机 知乎 编辑:程序博客网 时间:2024/05/16 06:20
本章是从整个对象的全局观来审视C++,并对比C++和C,讨论出一个较为高效和简明的C++对象模型,最后详细介绍了继承如何对程序造成影响。
主要分为以下几个内容:
- 引入。C++如何基于C构建对象
- C++简单对象的内存布局模型讨论
- Class 和 Struct 间的关系和影响
- 详细讨论对象的继承
关于C++的强大以及C的简洁之间的讨论已经很多,因而我不打算介绍C++是如何基于C构建对象的。而是直接进入C++对象的内存布局模型的讨论。
由于本章是C++对象模型的概述,也是全书的一个热身。我打算比较详细的介绍,故本文将只介绍2. C++简单对象的内存布局模型讨论。
今后再继续后面的讨论。
对象布局
我经常听人说,利用函数指针,他可以利用C实现面向对象。
诚然如此,但用C实现面向对象,以及C++内建的面向对象之间的执行效率有何区别?
如果不深入底层,我们是无法讨论出个结果的,现在,我就将简要的介绍书中提到的三种内存布局模型。
注:以如下类定义为例:
class Point{public: Point( float xval ) : _x(xval) {} virtual ~Point(); float x() const { return _x; } static int PointCount() { return _point_count; }protected: virtual ostream& print( ostream &os ) const { os << _x; return os; } float _x; static int _point_count;};
简单对象模型
这个模型非常简单,每一个对象有自己独立的存储空间,而存储的内容是一列指针(书中称存储此指针的空间为slot),每个指针指向该对象的一个成员变量或成员函数。如下图:
由此图可以清楚的看到这种模型的实现方式。
这种模型很简单,每个slot都是指针,因而有固定的大小。
但问题是,每次访问其成员,都必须加一层指针访问。这将极大的影响模型的效率。
表格驱动对象模型
这种模型相对于前一个模型,更具扩展性,它是在对象的存储空间中,只存储两个指针:一个指向成员变量表,一个指向成员函数表。成员变量表直接存储变量,函数表存储函数指针:
这种模型的好处在于,每个对象的存储空间都有相同的表现方式,扩展性非常强。
但实际上,这种模型比前一种模型效率更低。但是,在后面可以看到,成员函数表的模型将被应用于虚函数的实现上。
C++对象模型
这种模型是C++编译器实际使用的模型(不同的编译器只有细节差别,基本模型是一致的)。它对空间、时间都进行了优化,当然,这种优化也必然导致它的复杂度有些许增加。
书中讲述的比较简洁,可能有些难懂,我在这里分步骤来介绍这个模型。
首先,暂时不考虑虚函数的实现,那么其实现方式如下:
- 每个对象直接存储非静态变量,这与C中的struct完全一致
- 对象的静态变量存储在一块共享区域,每个对象都可以访问
- 对象的静态、非静态函数的执行体都存储在对象之外,每个对象都可以访问
- 静态及非静态函数的区别在于,非静态函数将多传递一个this指针,以便函数访问this指针指向的内容
其模型如下图:
图中可以清晰的看到,实际的动态内存需求,仅仅只是每个对象内部的非静态成员变量所占内存大小的总和。
并且,编译器内部访问对象的成员函数的方式,实际上是同C的函数访问完全一致的。
我们可以这样理解C++编译器的实现:
它将C++代码转换成了如下的C代码(暂时忽略虚函数):
// Point类定义,只有非静态成员变量struct Point{ float _x;};// 静态成员变量定义在Point类之外static int _Class_Point_point_count;// 构造函数初始化成员变量void _Class_Point_Constructor(struct Point* this, float xval) { this->_x = xval;}float _Class_Point_x(struct Point* this) { return this->_x;}// 静态成员函数直接访问静态成员变量static int _Class_Point_PointCount() { return _Class_Point_point_count;}
之后,再进行编译。
这样,一个不含虚函数的对象的存储空间,就与此对象成员变量组成的结构体毫无区别。
并且,对成员函数的访问效率,与直接访问一个C函数的效率等同。这一点,是使用函数指针模拟对象的C结构体无法比拟的。
C++对象模型(加虚函数)
下面我们再来看看加上虚函数的情形,它将在前文提到的实现上再加上几条:
- 一个包含虚函数的类,将定义一个虚函数列表,称为vtbl。此列表的每一个slot都指向该类的一个虚成员函数。
- 每个对象增加一个指针,指向对应类的虚函数列表。此指针称为vptr。这个vptr相当于一个变量。但只由编译器自己管理(在构造、析构和复制函数中设定和重置),用户不能访问。同一个类的不同对象,将包含指向同一个vtbl的vptr,并且,不会随着对象指针类型的变化而变化(后文再详细讨论)。
其模型结构如下图:
相对于前文的非虚函数类,增加了一个__vptr__Point的指针以及虚函数列表。图中所示的type_info for Point用于RTTI,暂不考虑。
此模型,翻译成c语言实现,将有如下定义:
struct Point;// Point类的虚函数定义ostream& _Class_Point_print(struct Point*, ostream&);void _Class_Point_Destructor( struct Point* );// Point类的虚函数表vtblstatic struct __vtbl__Point_Tag{ ostream& (*_p_Class_Point_print)( struct Point*, ostream& os ); void (*_p_Class_Point_De_Point)( struct Point* );} __vtbl__Point = { _Class_Point_print, _Class_Point_Destructor };// Point类(只有非静态成员变量)struct Point{ float _x; struct __vtbl__Point_Tag *__vptr;};// Point类的静态成员变量static int _Class_Point_point_count;// Point类的非静态成员函数,加上了this指针。// 在构造函数中初始化变量以及vptrvoid _Class_Point_Constructor(struct Point* this, float xval) { this->_x = xval; this->__vptr = &__vtbl__Point;}float _Class_Point_x(struct Point* this) { return this->_x;}// Point类的静态成员函数,直接返回静态成员变量static int _Class_Point_PointCount() { return _Class_Point_point_count;}// Point类的虚函数实现ostream& _Class_Point_print(struct Point* this, ostream& os) { os << this->_x; return os;}void _Class_Point_Destructor( struct Point* this ) {}
需要注意的是,C语言没有访问控制(const、private、protect、public等),这些访问控制是在C++编译器编译时检查的。
下面我用一个简单的例子来说明,虚函数是如何做到正确的访问成员函数的。
首先,看看不用虚函数的情况:
class Animal { void bark() { cout << "。。妈妈没教我叫。。。" << endl; }};class Dog { void bark() { cout << "汪汪!" << endl; }};int main() { Animal *a = new Dog; a->bark(); return 0;}
输出的结果是“。。妈妈没教我叫。。。”。
而如果是使用虚函数的情况呢:
class Animal { virtual void bark() = 0;};class Dog { virtual void bark() { cout << "汪汪!" << endl; }};int main() { Animal *a = new Dog; a->bark(); return 0;}
输出当然就是“汪汪!”了。并且,使用虚函数可以强制Animal的bark被覆盖。相当于Animal成为一个接口,这也可以避免出现“妈妈没教它叫”的窘境。
那么,虚函数是如何实现这一效果的?我们还是从底层来看看。
先看非虚函数的C语言翻译版:
struct Animal { // 没有成员变量,定义空结构体};// Animal构造函数,什么都不做void _Class_Animal_Constructor(struct Animal* this) {}void _Class_Animal_bark(struct Animal* this) { cout << "。。妈妈没教我叫。。。" << endl;}class Dog { // 没有成员变量,定义空结构体};// Dog构造函数,什么都不做void _Class_Dog_Constructor(struct Dog* this) {}void _Class_Dog_bark(struct Dog* this) { cout << "汪汪!" << endl;}int main() { Animal *a = new Dog; // new 的是Dog,调用Dog的构造函数(此函数什么都没做) _Class_Dog_Constructor(a); _Class_Animal_bark(a); // a->bark(); return 0;}
再看看虚函数版本:
// Animal类的虚函数为纯虚函数,没有实现// Animal类的虚函数表vtblstatic struct _Class_Animal__vtbl_Tag{ void (*_p_Class_Animal_bark)( struct Animal* );} _Class_Animal__vtbl = { 0 };struct Animal { // 只有一个vptr的变量 struct _Class_Animal__vtbl_Tag *__vptr;};// 在构造函数中初始化变量以及vptrvoid _Class_Animal_Constructor(struct Animal* this) { this->__vptr = &_Class_Animal__vtbl;}// Animal类的虚函数为纯虚函数,没有实现/////////////////////////////////////////////////////// Dog类的虚函数定义void _Class_Dog_bark( struct Dog* );// Dog类的虚函数表vtblstatic struct _Class_Animal__vtbl_Tag _Class_Dog__vtbl = { _Class_Dog_bark };struct Dog { // 只有一个vptr的变量 struct _Class_Dog__vtbl_Tag *__vptr;};// 在构造函数中初始化变量以及vptrvoid _Class_Dog_Constructor(struct Dog* this) { this->__vptr = &_Class_Dog__vtbl;}// Dog类的虚函数实现void _Class_Dog_bark(struct Dog* this) { cout << "汪汪!" << endl;}int main() { Animal *a = new Dog; // new 的是Dog,调用Dog的构造函数(此函数将a->__vptr赋值为_Class_Dog__vtbl _Class_Dog_Constructor(a); a->__vptr->_p_Class_Animal_bark(a); // a->bark(); return 0;}
代码中的关键在于虚函数版本中,Dog类的构造函数将a->__vptr赋值为_ClassDog__vtbl,而_ClassDog__vtbl->_p_Class_Animal_bark是赋值为_Class_Dog_bark的。因此,同样是a->__vptr->_p_Class_Animal_bark()的调用,初始化为Dog的Animal指针将访问的是_Class_Dog_bark,而没有使用虚函数的版本是直接调用了_Class_Animal_bark。这就是虚函数的实现方式。
虚函数的使用虽然多加上了一层指针访问,但其对动态访问的支持使得程序的设计可以更加抽象,有利于大型程序的设计。
- 《深度探索C++对象模型》阅读笔记(一)——对象的内存布局1
- [温故而知新] 《深度探索c++对象模型》——对象数据成员的内存布局
- C++对象模型简介(一)——《深度探索C++对象模型》精简笔记
- 深度探索c++对象模型-阅读笔记
- 《深度探索C++对象模型》阅读笔记(零)——引子
- 深度探索C++对象模型笔记<一>
- 深度探索c++对象模型——读书笔记(一)
- 深度探索C++对象模型笔记(一)
- 《深度探索C++对象模型》笔记(一)
- 深度探索C++对象模型笔记(一)
- 《深度探索C++对象模型》笔记(一)
- 深度探索C++对象模型——学习笔记1
- C++对象模型简介(二)——《深度探索C++对象模型》精简笔记
- 深度探索C++对象模型——Data Member的布局(2)书上的错误
- 深度探索C++对象模型———Data Member的布局
- 读《深度探索C++对象模型》之分层继承对于内存布局的影响
- 深度探索C++对象模型——学习笔记3
- 深度探索C++对象模型 关于对象的笔记
- 再读ecmascript
- 百度2013校园招聘笔试题
- 你所能用到的数据结构
- 关于C++多态的理解
- 黑马程序员_java数据库
- 《深度探索C++对象模型》阅读笔记(一)——对象的内存布局1
- bitmap索引的深入研究
- SQL 生成公曆和農曆對照數據續--创建萬年曆查找各種周期性節日數據
- Doctype? 严格模式与混杂模式-如何触发这两种模式,区分它们有何意义?
- Ubuntu下vim安装包区别
- 别了,松下忠洋;别了,日本人
- C#--第四周实验--任务3--定义一个人员类Cperson,在此基础上派生出学生类CStudent和教师类CTeacher,实现输入输出功能的成员函数。
- 做饭记
- js 获取浏览器IE、FF、Chrome、Opera、Safari