【转】http://www.cppblog.com/tiandejian/archive/2007/08/19/EC_26.html
第五章. 实现
在大多数情况下,恰当地做好类(以及类模板)的定义和函数(以及函数模板)的声明是整个实现工作的重中之重。一旦你顺利地完成了这些工作,那么相关的实现工作大都是直截了当的。然而,这里还存在一些需要关注的事情:过早地定义变量可能会牺牲性能。滥用转型可能会使代码变得笨重且不以维护,同时也会困扰于无尽难于发现的 bug 。返回一个对象内部内容的句柄会破坏代码的封装性,同时留给客户端程序员一个悬而未决的“野句柄”。如果对异常的影响考虑不周,那么将会带来资源泄露和数据结构的破坏。过分热衷于使用内联会使代码不断膨胀。代码文件过多过复杂会造成的过于复杂的耦合,程序的构建时间会漫长得让人无法忍受。
所有这些问题都是可以避免的。本章就来讲解如何去做。
第26条: 定义变量的时机越晚越好
你经常要使用构造函数或者析构函数来定义某个类型的一个变量,当系统控制在接收到这一变量的定义时,就引入了一次构造过程的开销;在变量达到自身作用域以外时,就引入一次析构过程的开销。未使用的变量也会带来一定的开销,所以你应该尽可能的避免这种浪费的出现。
你可能会想你永远也不会定义变量而不去使用,但是你可能需要三思而后行。请观察下边的函数,它在所提供的密码足够长时,可以返回一个加密版本的密码。如果密码长度过短,函数就会抛出一 个 logic_error 类 型的异常(这个异常类型定义于标准 C++ 库中,参见第 54 条):
// 这个函数定义 "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 并不是完全未使用的,但是在抛出异常的情况下,函数就不会使用它。也就是说,即使 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;
... // 对密码加密
return encrypted;
}
上面的代码还不像你想象的那么严谨,这是因为在定义 encrypted 是没有为它设置任何初始化参数。这就意味着编译器将调用它的默认构造函数。通常情况下,你要对一个对象需要做的第一件事就是为它赋一个值,通常是通过一次赋值操作。第 4 条中解释了为什么使用默认构造函数构造对象并为其赋值,要比使用需要的值对其进行初始化的效率低一些。那里的分析符合此处的情况。比如说,可以假设的较困难的部分是通过下面的函数来解决的:
void encrypt(std::string& s); // encrypts s in place
encryptPassword 就应该以下面的方式来实现了,尽管它不是最优秀的:
// 这一函数推迟了 enctypted 定义的时机,直到需要时才进行。
// 但仍然会带来不必要的效率问题。
std::string encryptPassword(const std::string& password)
{
... // 同上,检查密码长度
std::string encrypted; // encrypted 的默认构造函数版本
encrypted = password; // 对 encrypted 赋值
encrypt(encrypted);
return encrypted;
}
更好的一种实现方式是,使用 password 来初始化 encrypted ,这样就可以跳过默认构造过程所带来的无谓的性能开销:
// 最后给出定义和初始化 encrypted 的最佳方法
std::string encryptPassword(const std::string& password)
{
... // 检查长度
std::string encrypted(password);// 通过拷贝构造函数定义和初始化
encrypt(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 的某个值 ) ;
...
}
这里我使用了 Widget 类型的对象,而不是 string 类型的对象,从而避免了进行构造、析构、或者对象赋值等过程带来的误差。
对于 Widget 的操作而言,上面两种方法所带来的开销如下:
l 方法 A : 1 个构造函数 + 1 个析构函数 + n 次赋值。
l 方法 B : n 个构造函数 + n 个析构函数。
对于那些一次赋值操作比一对构造 - 析构操作开销更低的类而言,方法 A 是较高效的。尤其是在 n 较大的情况下。否则方法 B 就是更好的选择。还有,方法 A 使得 w 位于一个比方法 B 更大的作用域中,这是违背程序的可读性和可维护性原则的。因此,除非你确认 : (1) 赋值操作比一对构造 - 析构操作更高效, (2) 当前代码是对性能敏感的;其他任何情况下,你都应该使用方法 B 。
铭记在心
l 定义变量的时机越晚越好。这可以提高程序的清晰度和工作效率。