《深度探索C++对象模型》读书笔记,一

前言

今年中下旬就要找工作了,我计划从现在就开始准备一些面试中会问到的基础知识,包括C++、操作系统、计算机网络、算法和数据结构等。C++就先从这本《深度探索C++对象模型》开始。不同于《Effective C++》,这本书主要着眼于C++实现的底层机制,因此我在写这个系列时默认读者已经熟悉C++的基本语法(包括类、继承、多态、泛型等等),将更多地介绍C++具体是如何实现这些语法的。这次我就先写第一、二章,之后每读两章都会更新该系列。如果你有什么问题,欢迎在博客的评论版块和我探讨,或者联系我的邮箱:kevinstigma@gmail.com,很乐意与你互相切磋、提高。

第一章:关于对象

在传统的C程序中,采用的是过程式的思维:“数据”和“处理数据的操作(函数)”是分开来声明的,它们二者之间并没有关联性。但到了C++里,倾向于采用独立的“抽象数据类型”(ADT)来实现数据的封装。从代码结构上来看,C++似乎比C要消耗更高的时间和空间成本,但事实未必如此。相比于C,C++在布局和时间上主要的额外负担是由virtual引起的,这分为两部分:

1.virtual function机制 用以支持一个有效率的“执行期绑定”。

2.virtual base class 用以实现“多次出现在继承体系中的base class,有一个单一而被共享的实例”。

接下来,书里介绍了三种的C++对象模式:简单对象模型、表格驱动对象模型、C++对象模型,其中第三种最为编译器所常用,因此在这里主要介绍第三种。

C++对象模型中,非nonstatic data members被配置于每一个class object之内,static data members、function members则被存放在class object之外。编译器为每一个class产生一个表格,表格里是一堆指向virtual functions的指针,该表格我们称之为virtual table(vtbl);每一个class object被安插一个指针,指向相关的virtual table,这个指针被称为vptr。vptr的设定和重置由constructor、destructor以及copy assignment运算符自动完成。每一个class所关联的type_info object也存放在virtual table之中。

该模型的优点是,它的空间和时间的效率较高;缺点在于,如果应用程序本身的代码未改变,而所用到的class object的nonstatic data members有所修改,那么应用程序的代码同样需要重新编译(因为内存分布发生了改变)。

在接下来的篇幅里,书中探讨了struct和class的区别,对于这个问题可以参考这篇博文:http://blog.csdn.net/omegayy/article/details/7470316。其实struct和class从语法本质上差别并不大,无非是二者的默认继承和默认成员访问级别不同。但从一般来说,我们习惯用struct来代表一些简单数据的集合,用class来代表更为复杂的封装、继承的数据。

与C不同,在C++中,对象的内存分布未必和类中成员的声明顺序一致,比如C++的编译器可能会将protected data members放在private data members前面存储,与声明顺序无关;同理,base classes和derived classes的data members的布局也未有谁先谁后的强制规定。但类中处于同一个access section中的数据,其内存分布顺序是按照声明顺序排列的,只是不同access section之间未必按声明顺序排列。

之后书中花了一些篇幅介绍多态的机制,我觉得熟悉C++的人应当对此并不难理解,在这里不再赘述。

第二章:构造函数语义学

本章主要介绍了C++中构造函数的生成机制。

首先是default constructor,C++的编译器真的会为每一个没有声明构造函数的类生成default constructor吗?其实并不是的。书中指出,只有四种情况,会造成“编译器必须为未声明constructor的classes合成一个default constructor”:

1.member constructor有defualt constructor;

2.base class具有default constructor;

3.该类具有virtual funciton;

4.该类具有一个virtual base class。

在合成的defualt constructor中,只有base class subobjcets和member class objects会被初始化,而所有其他的nonstatic data member(int、int*、char等类型)都不会被初始化。

之后是copy constructor。与defualt constructor一样,编译器并不是为所有无用户定义copy constructor的类都创建copy constructor,如上的四种情况下,编译器才会为class合成copy constructor;否则编译器会执行bitwise copy。

接下来书中介绍了NRV优化以及member initialization list。

对于NRV(Named Return Value)优化,我们可以看一个例子:

假设有一个foo函数:

X foo() 
{ 
    X xx; 
    if(...) 
        return xx; 
    else 
        return xx; 
}

经过编译器的优化后,代码改成如下样子:

void  foo(X &result)
{
    result.X::X();
    if(...)
    {
        return;
    }
    else
    {
        return;
    }
}

这样就省去了临时对象的默认构造函数、拷贝构造函数、析构函数的成本,从而有助于减少运行时间。另外,有一点要注意的是,NRV优化,有可能带来程序员并不想要的结果,比如当你的类依赖于构造函数或者拷贝构造函数的调用次数时。

关于member initialization list,我们首先需要知道需要它的时机:

1.当初始化一个reference member时;

2.当初始化一个const member时;

3.当调用一个base class的constructor,而它拥有一组参数时;

4.当调用一个member class的constructor,而它拥有一组参数时。

接下来需要注意的一点就是:list中的初始化顺序是由class中member的声明顺序决定的,而不是由initialization list中的排列顺序决定的!这是一个非常容易出错的地方。