27.尽可能延后变量定义式的出现时间

前言

 
只要你定义了一个需要构造和析构的成员变量,那么当程序的控制流转移到该变量定义式时,你就必须承担构造成本。同样地,当控制流离开其作用域时,你必须承担析构成本。即使这个变量从未被使用。


问题实例

 
你或许认为没人会定义一个不使用的变量。请看下述程序:

1
2
3
4
5
6
7
8
9
10
string encryptPassword(const string& password){
//加密文本串,如果串过短则抛出异常
using namespace std;
string encrypted;
if(password.length()<MinLength){
throw logic_error("Password is too short");
}
...
return encrypted;
}

在此例中,对象encrypted可能并未使用,所以最好延后其定义式,直到确实需要使用该对象为止:
1
2
3
4
5
6
7
8
9
10
string encryptPassword(const string& password){
//加密文本串,如果串过短则抛出异常
using namespace std;
if(password.length()<MinLength){
throw logic_error("Password is too short");
}
string encrypted;//肯定能被使用
...
return encrypted;
}

但上述代码仍然存在提升的空间,请注意encrypted对象虽然获得了定义但并没有值(执行了默认初始化),在Effective C++ 5中我们了解到“通过default构造函数构造然后再赋值”的行为比“直接指定初值”的效率要低,所以,正确的写法应当如下:
1
2
3
4
5
6
string encryptPassword(const string& password){
...//检查长度
string encrypted(password);//直接copy构造
encrypt(encrypted);//加密函数
return encrypted;
}

这才是延后变量定义式的出现时间的真谛:可能地延后变量的定义直到可以直接用某个具体值初始化它为止。


定义式与循环

 
如果变量仅在循环内部使用,那么是应该把它定义在循环内还是循环外?也就是说,写法A与写法B,哪一种的效率更高?

1
2
3
4
5
//写法A 循环外定义,循环内赋值
Widget w;
for(int i=0;i<n;++i){
w=..//赋值
}

1
2
3
4
5
//写法B 循环内定义
Widget w;
for(int i=0;i<n;++i){
w(...)//copy初始化
}

两种写法的成本如下:

  • A:1次构造,1次析构,n次赋值
  • B:n次构造,n次析构

那么显然地,如果赋值成本较低,那应当使用A。但A造成了w的作用域不仅仅在循环内,这可能会降低代码的可读性与易维护性。除非你必须要对效率进行优化,否则写法B相较于A更佳。


总结

 
应当尽可能延后变量定义式出现的时间,这不仅有利于效率,也提高了程序的可读性。