太快定义变量可能造成效率上的拖延;过度使用转型(
casts
)可能导致代码变慢又难维护,又招来微妙难解的错误;返回对象“内部数据之号码牌(handles)”可能会破坏封装并留给客户虚吊号码牌(dangling handles
);未考虑异常带来的冲击则可能导致资源泄露和数据败坏;过度热心地inlining
可能引起代码膨胀;过度耦合(coupling
)则可能导致让人不满意的冗长建置时间(build times
)
26 Postpone variable definitions as long as possible
- 尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率;
27 Minimize casting
const_cast
一般用于强制消除对象的常量性。它是唯一能做到这一点的C++
风格的强制转型;dynamic_cast
主要用于执行“安全的向下转型(safe downcasting)”,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。它是唯一不能用旧风格语法执行的强制转型,也是唯一可能有重大运行时代价的强制转型;reinterpret_cast
是特意用于底层的强制转型,导致实现依赖(implementation-dependent
)(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制转型在底层代码以外应该极为罕见;static_cast
可以被用于强制隐型转换(例如,non-const
对象转型为const
对象,int
转型为double
,等等),它还可以用于很多这样的转换的反向转换(例如,void*
指针转型为有类型指针,基类指针转型为派生类指针),但是它不能将一个const
对象转型为non-const
对象(只有const_cast
能做到),它最接近于C-style
的转换;const_cast
,这个转换能剥离一个对象的const
属性,也就是说允许你对常量进行修改;static_cast
,支持子类指针到父类指针的转换,并根据实际情况调整指针的值,反过来也支持,但会给出编译警告,它作用最类似C
风格的“强制转换”,一般来说可认为它是安全的;dynamic_cast
,支持父类指针到子类指针的转换,并根据实际情况调整指针的值,和static_cast
不同,反过来它就不支持了,会导致编译错误,这种转换是最安全的转换;reinterpret_cast
,支持任何转换,但仅仅是如它的名字所描述的那样“重解释”而已,不会对指针的值进行任何调整,用它完全可以做到“指鹿为马”,但很明显,它是最不安全的转换,使用它的时候,你得头脑清醒,知道自己在干什么;- 如果可以,尽量避免转型,特别是在注重效率的代码中避免使用
dynamic_cast
; - 如果必须要转型,请试着将其隐藏在某个函数背后;
- 使用
C++
新型的转换,不要使用旧式转型;
28 Avoid returning “handles” to object internals
- 避免返回
handles
(包括reference
、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助const
成员函数的行为像个const
,并将发生”虚吊号码牌”(dangling handles
)的可能性降至最低;
29 Strive for exception-safe code
- 异常安全函数(
Exception-safe functions
)即使发生异常也不会泄露资源或允许任何数据结构破坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型:
基本型:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态(例如所有的class
约束条件都继续获得满足);
强烈型:如果异常被抛出, 程序状态不改变。如果函数成功,就是完全成功,否则,程序会回复到“调用函数之前”的状态;
不抛异常型:承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能。作用于内置类型(如ints
,指针等等)上的所有操作都提供nothrow
保证; - “强烈保证”往往能够以
copy-and-swap
实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义; - 函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者;
30 Understand the ins and outs of inlining
- 它们看起来像函数,动作像函数,比宏好得多,可以调用它们又不需蒙受函数调用所招致的额外开销,会增加目标码大小;
inline
只是对编译器的申请,不是强制命令,可以隐性提出,也可以明确提出:
//隐性提出示例
class Person{
public:
...
int age() const{return theAge} //隐性inline申请,定义于类内的成员函数
...
private:
int theAge;
};
//显性声明inline函数的做法是在其定义式前加上关键字inline
template<typename T>
inline const T& std::max(const T& a, const T& b){
return a < b ? b : a;
}
Inline
函数通常一定被置于头文件内,因为大多数构建环境是在编译过程中进行inlining
;inline
在大多数C++
程序中是编译期行为;- 当创建一个对象,其每一个
base class
及每一个成员变量都会被自动构造;当销毁一个对象,反向程序的析构行为也会自动发生; - 大部分调试器面对
inline
函数都束手无策,因为无法在一个并不存在的函数内设立断点; inline
:执行前,先将调用动作替换为被调用函数的本体;virtual
:等待,直到运行期才确定调用哪个函数;- 实际上构造函数和析构函数往往都是
inlining
的糟糕候选人; - 将大多数
inlining
限制在小型及被频繁调用的函数身上。这可使日后的调试过程和二进制升级(binary upgradability
)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化; - 不要只因为
function templates
出现在头文件,就将它们声明为inline
;
31 Minimize compilation dependencies between files
- 支持“编译依存最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是
Handle classes
和Interface classes
; - 程序库头文件应该以“完全且仅有声明式”(
full and declaration-only forms
)的形式存在。这种做法不论是否涉及templates都适用;
相关博客:【1】