洛译小筑

别来无恙,我的老友…
随笔 - 45, 文章 - 0, 评论 - 172, 引用 - 0
数据加载中……

[ECPP读书笔记 条目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);     // 适时为s加密

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的操作而言,上面两种方法所带来的开销如下:

方法A:1个构造函数 + 1个析构函数 + n次赋值。

方法B:n个构造函数 + n个析构函数。

对于那些一次赋值操作比一对构造-析构操作开销更低的类而言,方法A是较高效的。尤其是在n较大的情况下。否则方法B就是更好的选择。还有,方法A使得w位于一个比方法B更大的作用域中,这是违背程序的可读性和可维护性原则的。因此,除非你确认: (1)赋值操作比一对构造-析构操作更高效,(2)当前代码是对性能敏感的;其他任何情况下,你都应该使用方法B。

时刻牢记

定义变量的时机越晚越好。这可以提高程序的清晰度和工作效率。

 

posted on 2007-08-19 22:00 ★ROY★ 阅读(1160) 评论(0)  编辑 收藏 引用 所属分类: Effective C++


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理