Item 26: Postpone variable definitions as long as possible.
这一节作者谈到的内容是关于变量的定义时机问题。需要考虑这样的一个问题的原因就在于当定义一个变量的时候,就意味着对这个变量对象进行构造(如果有构造函数),以及在离开它作用域的时候进行析构,也就是说需要承担这些消耗。那么如何避免没有意义的消耗就是一个值得考虑的话题
作者给出的建议就是尽可能的推迟变量的定义 来看例子
// this function defines the variable "encrypted" too soon
std::string encryptPassword(const std::string& password)
{
using namespace std;
string encrypted;
if (password.length() < MinimumPasswordLength) {throw logic_error("Password is too short");
}.
.. // do whatever is necessary to place an
// encrypted version of password in
encrypted
return encrypted;
}
上面的程序中encryptPassword是对传入的参数password进行加密,在函数中声明了一个作为返回的加密的string对象encrypted。
这种代码组织方式看上去没什么问题,但设想如果在后面的if语句中抛出了异常,这就意味我们前面定义的string encrypted没被用上,也就意味着我们白白付出了对string的构造以及后面的析构代价。
改进的方法,自然是不急着定义这个字符串,等到真正需要使用的时候才定义,从而减轻一定的损失可能性。就像下面的代码
// this function postpones encrypted's definition until it's truly necessary
std::string encryptPassword(const std::string& password)
{
using namespace std;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
string encrypted;
... // do whatever is necessary to place an
// encrypted version of password in encrypted
return encrypted;
}
把encrypted的定义放到异常抛出之后,也就是真正需要用的时候,因为下面就是对它进行处理。
看到这似乎已经把这一节的内容讲完了,但作者在后面继续对上面的代码进行了一些优化,其实也是对之前的内容的回顾。
上面的代码中,我们是定义了一个string encrypted,它将执行默认初始化,接着我们肯定需要对它处理,而且需要用到实参password,所以我们做的操作是下面这样的代码
// this function postpones encrypted's definition until
// it's necessary, but it's still needlessly inefficient
std::string encryptPassword(const std::string& password)
{
... // check length as above
std::string encrypted; // default-construct encrypted
encrypted = password; // assign to encrypted
encrypt(encrypted);
return encrypted;
}
它先默认初始化,再赋值,这其实意味着默认初始化白费了,更好的写法就是直接进行拷贝构造函数来省去无用功
// finally, the best way to define and initialize encrypted
std::string encryptPassword(const std::string& password)
{
... // check length
std::string encrypted(password); // define and initialize via copy constructor
encrypt(encrypted);
return encrypted;
}
通过这个例子作者是想告诉我们,有时候不仅是要尽可能的推迟变量定义到不得不用为止,更有可能是需要推迟到必须初始化从而避免默认初始化的无用消耗。
接着作者谈到了再循环中的情况,举出两个例子对比
// Approach A : define outside loop // Approach B : define inside loop
Widget w;
for (int i = 0; i < n; ++i){ for (int i = 0; i < n; ++i) {
w = some value dependent on i; Widget w( some value dependent on i );
... ...
} }
方法A中,将w放在循环外,需要一次默认初始化,最终也需要一次析构 然后循环中执行n次赋值 一共是一次构造,一次析构,n次赋值
方法B中,将w放在循环内,每次循环需要一次拷贝构造,但是由于是局部变量,每次循环也会产生一次析构,所以是n次构造,n次析构
如何比较两者性能?我们看这两个式子,起关键作用的是n后面的动作,也就是比较n次赋值和n次构造加析构的代价。
如果一次赋值的代价小于一次构造和一次析构,那方法A肯定的是高效 否则方法B更好,而且方法A的问题在于w的作用域更广,不利于理解和维护。所以综上给出的建议就是:
1. 知道一次赋值的代价小于一次构造和析构,而且这段代码对性能要求很高,我们才选择A方法
2. 其他情况选择B方法