Effective C++ 第五章——实现

介绍了条款26~31

Posted on May 30, 2017 in Effective_Cpp, ReadBook



条款26:尽可能延后变量定义式的出现时间

条款27:尽量少做转型动作

条款28:避免返回handles指向对象内部成分

条款29:为“异常安全”而努力是值得的
条款30:透切了解inlining的里里外外
条款31:将文件间的编译依存关系降至最低


条款26:尽可能延后变量定义式的出现时间

凡是构造出一个对象都要至少一个构造与析构的过程,这个会消耗资源的,所以千万不要定义一个不用的变量。 
而且比较推荐当获取到初值实参的时候再声明初始化变量,而不是先构造空对象再赋值,于是:

你不只应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参为止。

P115 对于循环体而言,可能先声明初始化出变量更好。

条款27:尽量少做转型动作

旧式的转型操作:

1、(T)expression: C风格转型动作

2、T(expression) : 函数风格的转型动作

新式转型动作:

1、const_cast(expression) 将对象的常量性转移

2、dynamic_cast(expression) “安全向下转型”,也就是用来决定某对象是否归属于继承体系中的某个类型。消耗最大的转型动作。

3、reinterpret_cast(expression) 意图执行低级转型,其实际动作取决于编译器,不可移植。类型将pointer to int转型为int

4、static_cast(expression) 强迫隐式转换,可以 
    4.1.non-const to const 
    4.2.int to double 
    4.3.void*指针到typed指针 
    4.4.pointer-to-base 到 pointer-derived

使用新式转型的好处:

1.容易在代码中辨认

2.各种转型动作的目标专业化,愈窄化

P118 转型操作并非什么都没有做,编译器需要编译出代码,是需要占用资源的 
P119 转型操作是构造出一个副本而非直接改变本对象,如距离的派生类某funA()需要执行基类的funA()的话,不要使用转型

讨论一下,dynamic_cast 
什么时候需要使用它:

你想在一个你认定为derived class对象身上执行derived class操作函数,但是手头上却只有一个“指向base”的pointer或reference.

如何消除dynamic_cast的使用:

1.使用容器并在其中存储直接指向derived class对象的指针(智能指针)

2.通过base class接口处理“所有可能之各种派生类”,那就是在base class内提供virtual函数做你想对各个派生类做的事。但可以缺省实现代码的P122

避免连串的dynamic_casts

other建议:如果转型是必须的,试着将其隐藏在函数背后而不用让用户自己去调用。

条款28:避免返回handles指向对象内部成分

handles是指reference、指针或者迭代器等,若返回指向内部资源的指针,那么会降低类的封装性,将private级别的变量变为public,可以将函数返回值定义为const,这样可以让渡读取权而依然禁止涂写权。

返回handles的另一个危险之处是会返回空悬指针P126

条款29:为“异常安全”而努力是值得的

“异常安全”的函数会:

1.不泄露任何资源

2.不允许数据被破坏

解决不泄露资源问题可以采用资源管理:可以参看《资源管理》一文

在讨论不允许数据被破坏之前,先看下面的术语: 
P128异常安全函数提供一下保证之一:

1.基本承若

2.强烈保证

3.不抛掷保证

P130针对开篇的例子,可以运用资源管理的办法解决不允许数据被破坏以达到强烈保证 
但是常用的办法是copy and swap P131,在那里还简短介绍了pimpl idiom

异常安全性还具有连带影响,一个函数的异常安全性取决于其所调用之各个函数的“异常安全保证”中的最弱者。

强烈保证中采取copy and swap 可能比较耗资源,可以适当选择采用基本承若等。

条款30:透切了解inlining的里里外外

inline函数的优点:

 1.免除函数调用成本

 2.编译器有能力对它进行语境相关的最优化处理

缺点:

 1.增加目标代码的大小,降低高速缓存装置的命中率,带来效率的损失

 2.析构与构造函数不能inline,因为其实现代码比你能看的要多,即使在什么都不写的情况下

 3.程序库的设计者需要考虑inline对代码更新带来的冲击

 4.大部分调式器对inline束手无策

如何inline: 
 一开始先不要inline 
 需要优化代码的时候,对有效增进程序整体效率的那部分代码进行inline

inline的隐与显:

 1.函数体的定义写在class内是隐式的inline

 2.采用inline关键词的是显式的inline

inline函数通常是置于头文件内,templates通常也是置于头文件内但是与inline无关

什么函数编译器会拒绝inline:

 1.带循环或者递归的函数

 2.virtual函数

 3.通过指针调用的函数

条款31:将文件间的编译依存关系降至最低

通过includes,c++中的依存关系十分强烈,用前置声明来替代includes又有下面的问题:

1.正确的前置声明比较复杂,因为涉及额外的templates

2.编译器必须在编译期间知道对象的大小,因为C++是值语义而非对象(引用)语义,当然也可以通过pimpl idiom来实现对象的引用语义

为了达到条款31的目标,可以

1.如果使用object references 或 object pointers可以完成任务,就不要使用objects

2.如果可以,尽量以class的声明式替换class定义式,就是说用前置声明替换includes

3.声明式与定义式提供不同的头文件

pimpl idiom的classes往往被称为Handle classes,其Impl类与原本的类有着相同的接口

另一个制作Handle class的方法是Interface class抽象类,其比java的接口不同的是可以在类中实现成员函数与成员变量 
抽象类中可以有static的工厂函数(这样抽象类就叫Interface classes了),返回派生类的指针。于是乎,抽象类(Interface classes)是接口,派生类是实现

Handle classes与Interface classes解除了接口与实现之间的耦合关系,从而降低文件的编译依存性。 
在Handle classes身上,成员函数必须通过implementation pointer取得对象数据,那么为每一次访问增加一层间接层 
至于Interface classes,由于每个函数都是virtual,所以你必须为每次函数调用付出一个间接跳跃成本