function语意学和析构函数语意学

此篇为<<deep in C++ object model>>读书笔记。


function语意学

小思考题

Point3d obj;

Point3d *ptr = &obj;

下面的区别:

obj.normalize();

ptr->normalize();

明确地调用point3d实体会比较有效率,并因此压制由于虚机制而产生的不必要的重复调用操作。


Nonstatic Member Functions

C++的设计准则之一就是:nonstatic member function至少必须和一般的nonmember function有相同的效率。实际上member function在编译的时候被内化为non member的形式,步骤如下:

1.修改函数原型,将this指针插入作为函数参数;

2.对每一个nonsstatic data member的存取操作改为经由this指针来存取;

3.将member function重新写一个外部函数。对函数名称进行mangling处理,使它在程序中成为独一无二的词汇;


使用一个member function指针,如果不用virtual function,多重继承,virtual base class等情况的话,并不会比使用一个 nonmember function 指针的成本更高。


Static Member Functions

1.不能够直接存取其class中的nonstatic members;

2.不能够被声明为const volatile或virtual;

3.不需要经由class object才能被调用。


虚拟成员函数

virtual function的一般实现模型:每个class有一个virtual table,内含该class之中有作用的virtual function地址,每个object有一个vptr,指向virtual table的所在。

有了RTTI,就能够在执行期间查询一个多态的point或是多态的pointer了。

        欲鉴定哪些classes展现多态特性,需要执行期信息。识别一个class是否支持多态,唯一适当的方法就是看看它是否有任何 virtual function,只要class拥有一个virtual function,就需要这份额外的执行期信息。

       多重继承下,一个derived class内含n-1个额外的virtual tables。n表示其上一层basee classed 的数目。正对每一个virtual table,derived对象中有对应的vptr,vptrs将在constructor中被设立初值。


Inline function

         关键字inline知识一项请求。如果这项请求被接受,编译器就必须认为它可以用一个表达式合理地对待这个函数展开。

          inline函数在编译器的实现分两个阶段:

1. 分析函数定义,求复杂度;

2. 真正的inline函数扩展操作是在调用的那一点上,这会带来参数求值操作以及临时对象的管理。


inline函数对于封装提供了一种必要的支持,可以有效地存取封装于class中的non-public数据,它是#define的良好的替代品,特别是有副作用的宏。然而一个inline函数如果被调用太多次数的话,会产生大量扩展码,是程序臃肿。


析构、拷贝

          纯虚函数可以被定义,但只能被静态调用。

          纯虚析构函数一定要定义它,因为每一个derived class destructor会被编译器加以扩展,以静态调用的方式调用其,诶一个virtual base class以及上一层base class 的destructor。

          global object的声明和整个程序的声生命相同。C++中的所有全局对象都被当作初始化过的数据来对待。

         const对象的数据成员在对象寿命期内不能改变,const对象只能调用const成员函数。


对象复制语意学

        将copy assignment operator 声明为private 并且不提供其定义,就可以不允许于任何地点进行赋值拷贝操作。

         只有在默认行为所导致的语意不安全或不正确时,我们才需要设计一个copy assignment operator。

Bitwise copy = 浅拷贝,memberwise copy = 深拷贝。


一个class对于默认的copy assignment opeerator在以下情况下不会表现出bitwise copy语意:

1.当class内带一个member object,而其data有一个copy assignment operator时;

2.当一个class的base class 有一个copy assignment operator时;

3.当class声明了任何virtual functions;

4.当class继承自一个virtual base class。


copy constructor 的出现不应该让我们认为也提供一个copy assignment operater。


析构函数语意

        如果class 没有自定义destructor,那么只有在class 内带的member object(或是class自己的bass class)拥有destructor的情况下,编译器才会自动合成一个出来。 

        一个程序员定义的destructor被扩展的方式类似constructors被扩展的方式,但顺序相反:

1.如果对象内带vptr,那么首先reset相关的virtual table;

2.destructor的函数体被执行;

3.如果有任何直接的上一层的非虚基类,拥有destructor,它们会以其声明顺序的相反顺序被调用;

4.如果有任何virtual base classes拥有destructor,当前这个class时最尾端的class,那么它们会以其原来的构造顺序的相反顺序被调用。


执行期语意学

对象的构造和析构

       一般而言会把object尽可能放置在使用它的那个程序段附近,这样做可以节省不必要的对象产生操作和堆摧毁操作。


全局对象

        C++一定会在main()中第一次用到全局变量之前把全局对象构造出来,而在main函数结束之前把变量摧毁掉。存放在data段中,需要静态的初始化操作和内存释放操作,如果明确制定给它一个值,object将以该值为初值,否则object所配置到的内存内容为0,constructor一直要到程序激活时才会实施。


静态初始化地缺点

1. 无法堆构造函数进行异常处理;

2. 为了控制“需要跨越模块做静态初始化”,objects的相互依赖顺序会增加复杂度。


局部静态变量

需要保证语意:

1.constructor 必须只能实行一次,岁但可能会调用多次;

2.destructor必须只施行一次,虽然可能调用多次。


全局对象 VS 局部静态对象

用于     

全局:1) 定义于namespace的对象;2)file里的static对象;3)类中的静态变量和全局变量。

局部:函数体内的static变量。


初始化时机

全局:1)在main函数的代码前进行初始化;2)类的静态成员和全局对象与该类对象并无关系;

静态数据成员在类声明之前声明,在包含类方法的文件中初始化,初始化使用作用域操作符来指出静态成员所属类,位于全局静态区。

局部:函数被调用,首次碰到该定义的时候进行初始化。


底层实现

全局:对每个文件里需要初始化的静态变量,生成一系列sti_开头的函数,在里面完成初始化的调用语句,放到main里的开头。

局部:附加一个全局变量,标志该对象是否被初始化么,同时在析构时检查这个变量,保证第一次碰到时初始化,同时只有在执行了构造函数的时候,才会在程序退出时执行对应的析构函数。


读后感

       慕名已久的一本书,侯捷老师翻译的,我只能用两个字评价:好书!

       看这本可能会设计一些编译器的知识,而且对C++应该时有一定的理解才可以。在有一定的语言基础之后,看这本书绝对能够打通你的C++的任督二脉。

        如果想深入理解C++,这本书可谓必不可少,专修内功。


你可能感兴趣的:(C++)