只要你定义了一个变量而其类型带有一个构造函数或析构函数,那么当程序控制流(control flow)到达这个变量定义式时,你便得承受构造成本;当这个变量离开其作用域时,你便得承受析构成本。即使这个变量最终未被使用,仍需耗费这些成本,所以你应该尽可能避免这种情形。
或许你会认为,你不可能定义一个不使用的变量,但话不要说得太早!考虑下面这个函数,它计算通行密码的加密版本而后返回,前提是密码够长。如果密码太短,函数会丢出一个异常:
// 这个函数过早定义变量“encrypted”
std::string encryptPassword(const std::string& password)
{
using namespace std;
string encrypted;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
... // 必要动作,能将一个加密后的密码置入变量encrypted内
return encrypted;
}
对象encrypted在此函数中并非完全未被使用, 但如果有个异常被丢出,它就真的没被使用。也就是说如果函数encryptPassword丢出异常,你仍得付出encrypted的构造和析构成本。所以最好延后encrypted的定义式,直到确实需要它:
// 这个函数过早定义变量“encrypted”
std::string encryptPassword(const std::string& password)
{
using namespace std;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
string encrypted;
... // 必要动作,能将一个加密后的密码置入变量encrypted内
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的某个值);
...
}
这里把对象类型从string改为Widget,以免造成读者对于“对象执行构造、析构、或赋值动作所需的成本”有任何特殊偏见。
在Widget函数内部,以上两种写法的成本如下:
- 做法A:1个构造函数+1个析构函数+n个赋值操作
- 做法B:n个构造函数+n个析构函数
如果classes的一个赋值成本低于一组构造+析构成本,做法A大体而言比较高效。尤其当n值很大的时候。否则做法B或许较好。此外做法A造成名称w的作用域(覆盖整个循环)比做法B更大,有时对程序的可理解性和易维护性造成冲突。因此除非:(1)你知道赋值成本比“构造+析构”成本低;
(2)你正在处理代码中效率高度敏感(performance-sensitive)的部分,否则你应该使用B。
请记住
- 尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率。
通常项目开发过程中,为了提高代码可读性,可维护性,所以基本上会选择上述"做法B"方案,就像我们设计程序的时候,当成员变量和成员函数不确定具体访问权限的时候,先用private修饰,即使后续修改也容易得多,不容易被乱用,导致其它问题。