effective C++读书笔记(五)

来源:互联网 发布:跟踪软件 编辑:程序博客网 时间:2024/04/25 18:37

5. 实现(Implementations)

条款26:尽可能延后变量定义式的出现时间(Postponevariable definations as long as possible)

本条款主要讲:如果你定义了一个变量且该类型带一个构造函数或析构函数,当程序到达该变量时,你要承受构造成本,而离开作用域时,你要承受析构成本。为了减少这个成本,最好尽可能延后变量定义式的出现时间。同时对于循环中的变量定义来说,参考书中关于定义方式的博弈,尽量采用在循环内部定义

尽可能延后不只是应该延后变量的定义,直到非得使用它的前一刻,甚至应该尝试延后这份定义直到能够给它初始实参为止。这样不仅能够避免构造(析构)非必要对象,还可以避免无意义的default构造行为。

 

条款27:尽量少做转型(Minimizecasting)

 C++规则的设计目标之一是,保证类型错误绝不可能发生。不幸的是,转型(casts)破坏了类型系统。那可能导致任何种类的麻烦,有些容易辨识,有些非常隐晦。本条款介绍了C++的四种转型方式,并建议程序员尽量少做转型。

 C风格的转型动作看起来像这样:
     (T)expression    //
expression转型为T
    
函数风格的转型动作看起来像这样:
     T(expression)    //
expression转型为T

C++提供了四种新式转型(C++style casts)

1const_cast<T>(expression):移除变量的const属性;

2reinterpret_cast<T>(expression):执行低级转换,处理互不相关类型的转换。例如从intpointerto int

3static_cast<T>(expression):用来强迫隐式转换,完成相关类型之间的转换。例如,同一个类层次结构中一个指针类型到另一个指针类型、intdoublenon-const对象转换为const对象等;

4dynamic_cast<T>(expression):主要用来执行安全向下转型”(safedowncasting)。通常是你想在一个你认定为derivedclass对象身上执行derivedclass操作函数,但你手上却只有一个指向base”pointerreference,只能靠dynamic_cast来处理对象。此外,dynamic_cast也可以用来执行向上强制和交叉强制。

对于这几种类型转换,给出的建议是:

1)如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast,可以用使用类型安全容器virtual函数往继承体系上方移动的方法替代dynamic_cast

2)宁可使用C++style转型,不要使用旧式转型(即C语言风格的转型(T)expressionT(expression))。前者容易识别出来,而且也比较有分门别类的指掌。

 

条款28:避免返回handles指向对象内部成分(Avoidreturning”handles” to object internals)

不要在类的成员函数中返回handles(包括references、指针、迭代器)指向对象内部成员变量,因为这样可以增加封装性(避免private内部数据被修改),帮助const成员函数的行为像个const,防止空悬、虚吊(dangling)对象(即:handle比其所指对象更长寿)。

有一个概念就是const函数最好返回const对象,这点倒是比较有用。

const Point& upperLeft( )const { return pData->ulhc; }

 

条款29:异常安全努力是值得的(Strivefor exception-safe code)

提供了防止异常的几个方法以对象管理资源,可以阻止资源泄漏;copyand swap策略:对打算修改的对象(原件)做一个副本,在副本上做修改,修改完成后,将副本和原对象在一个不抛出异常的操作中置换。

异常安全函数提供以下三个保证之一(意思是,已经是异常安全,满足上面两个条件,在这个基础上再谈问题)

1、基本承诺:异常被抛出,程序内仍然保持有效状态(说白了就是满足上面两个条件)

2、强烈保证:异常回到以前的状态(要么成功,要么回到解放前)

3、不抛掷异常

函数提供的异常安全保证通常最高只等于其所调用之各个函数的异常安全保证中的最弱者

 

条款30:透彻了解inlining的里里外外(Understandthe ins and outs of inlining)

Inline函数可免除函数调用成本,提高程序执行效率,但它也会带来负面影响:(1)增大目标代码的大小。即使使用虚拟内存,inline造成的代码膨胀会导致额外的换页行为,降低cache命中率,降低程序执行速度。(2inline函数无法随着程序库的升级而升级。换句话说如果f是程序库内的一个inline函数,客户将“f 函数本体编进其程序中,一旦程序库设计者决定改变f所有用到f的客户端程序都必须重新编译。然而,如果fnon-inline函数,只需要重新连接就好了。

Inline函数无法设立断电,因此在调试版程序中禁止发生inline。这使得我们在决定哪些函数应该被声明为inline而哪些函数不该时,掌握一个合乎逻辑的策略。一开始先不要将任何函数声明为inline,或至少将inline施行范围局限在那些“一定成为inline”或“十分平淡无奇”的函数身上

1、将大多数inlining限制在小型、被频繁调用的函数身上才是最明智的选择(根据80-20经验准则,80%的执行时间花在20%的代码上。所以,我们的目标是找到那20%的代码,将它inline并竭力优化)。

2、不要只因为functiontemplate出现在头文件,就将它们声明为inline

 

条款31: 将文件间的编译依存关系降至最低(Minimizecompilation dependencies between files)

编译依存最小化的基本思想是:相依于声明式,而不是定义式。主要手段是:HandleclassesInterfaceclasses

1 Handleclassmain class内含一个指针成员,指向其实现类。这般设计常被称为pimplidiom (pimpl ”pointerto implementation”的缩写,这种class称为“Handleclass”。例如下面的代码:

#include <memory>//为了std::tr1::shared_ptr而含入

class Personimpl; //Person实现类的前置声明

class Person
public:
 ...
private;
 std::tr1::shared_ptr<Personimpl>PImpl;  //指针,指向实现物
};

2Interfaceclasses,就是抽象基类。

·        支持编译依存性最小化的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是HandleclassedInterfaceclasses

·        程序库头文件应该以完全且仅有声明式fulland declaration-only forms)的形式存在。这种做法不论是否涉及templates都适用。 

原创粉丝点击