条款26:Postpone variable definitions as long as possible
只要你定义了一个类变量,你就得承受相应的构造和析构成本——即使该变量最终未被使用。或许你觉得你不会定义一个不使用的变量,那不妨考虑下面这个函数,它返回加密后的密码,但如果密码太短,函数会抛出一个类型为logic_error的异常:
//这个函数过早定义变量"encrypted"
std::string encryptPassword(const std::string& password) {
using namespace std;
string encrypted;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
... //必要处理
return encrypted;
}
如果有异常抛出,对象encrypted就未被使用,其构造和析构成本就不是必要的。所以,最好延后encrypted的定义,直到确实需要它:
//这个函数延后"encrypted"的定义,直到确实需要它:
std::string encryptedPassword(const std::string& password) {
using namespace std;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
string encrypted;
.. //必要处理
return encrypted;
}
但是这段代码也并非秾纤合度,因为encrypted虽获得了定义却没有任何实参作为初值。这意味着调用的是其default构造函数。许多时候你该对对象做的第一次事就是给它一个初值。条款4曾解释通过default构造函数构造出一个对象然后对它赋值比直接在构造时指定初值效率差,因此你不只应该延后变量的定义直到使用该变量为止,甚至应该延后定义知道能够给其初值位置。这样不仅能够避免构造和析构非必要对象,还可以避免无意义的default构造行为。
假设上述函数在对变量"encrypted”处理之前以"password“为初值,那么定义"encrypted"的最佳做法应该如下:
std::string encryptedPassword(const std::string& password) {
... //检查长度
std::string encrypted(password);
... //必要处理
return encrypted;
}
如果碰到循环怎么办呢?如果变量只在循环内使用,那么该把它定义于循环外并在每次迭代时赋值给它,还是把它定义于循环内?下面两个结构,哪个比较好?
//方法A:变量定义于循环外
Widget w;
for (int i = 0; i < n; ++i) {
w = 取决于i的某个值;
...
}
//方法B:变量定义于循环内
for (int i = 0; i < n; ++i) {
Widget w(取决于i的某个值);
...
}
考虑两种方法的成本:
方法A:1个构造函数+1个析构函数+n个赋值操作
方法B:n个构造函数+n个析构函数
如果赋值成本低于构造+析构成本,做法A比较高效,特别是n很大时。但A中w的作用域比B中更大,这有时会降低程序的可读性和可维护性。因此除非你知道赋值成本比构造+析构成本低,并且正在处理的代码对效率的要求很高,否则你应该使用方法B。
条款27:Minimize casting
首先让我们回顾一下转型的语法。为将expression转型为T
C风格的转型如下:
(T)expression
函数风格的转型如下:
T(expression)
以上两种形式并无差别,纯粹只是小括号的位置不同。我们称这两种形式为旧式转型。
C++还提供四种新式转型:
const_cast<T>( expression )
dynamic_cast<T>( expression )
reinterpret_cast<T>( expression )
static_cast<T>( expression )
它们的作用各不相同:
- const_cast用来移除对象的常量性。它是唯一一个有此能力的C++-style转型方式。
- dynamic_cast用来执行安全向下转型。它是唯一无法由旧式转型执行的动作,也是唯一可能耗费重大运行成本的转型动作
- reinterpret_cast用来执行低级转型,结果往往取决于编译器,这意味着它不可移植。
- static_cast用来进行显式类型转换。