C 箴言:只要有可能就推迟变量定义

2008-02-23 05:40:37来源:互联网 阅读 ()

新老客户大回馈,云服务器低至5折

  在极大程度上,为您的类(包括类模板)和函数(包括函数模板)提供正确的定义是战斗的关键性部分。一旦您得到正确的结果,相应的实现很大程度上就是直截了当的。但是仍然有一些注意事项需要当心。过早地定义变量会对性能产生拖累。过度使用强制转换会导致缓慢的,难以维护的,被微妙的 bug 困扰的代码。返回一个类内部构件的句柄会破坏封装并将空悬句柄留给客户。疏忽了对异常产生的影响的考虑会导致资源的泄漏和数据结构的破坏。过分内联化(inlining)会导致代码膨胀。过度的耦合会导致令人无法接受的漫长的建构时间。 这一切问题都能够避免。

  只要有可能就推迟变量定义

  只要您定义了一个带有构造函数和析构函数的类型的变量,当控制流程到达变量定义的时候会使您担负构造成本,而当变量离开作用域的时候会使您担负析构成本。假如有无用变量造成这一成本,您就要尽您所能去避免他。

  您可能认为您从来不会定义无用的变量,但是也许您应该再想一想。考虑下面这个函数,只要 password 的长度满足需要,他就返回一个 password 的加密版本。假如 password 太短,函数就会抛出一个定义在标准 C 库中的 logic_error 类型的异常(参见 Item 54):

// 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;
}

  对象 encrypted 在这个函数中并不是完全无用,但是假如抛出了一个异常,他就是无用的。换句话说,即使 encryptPassword 抛出一个异常,您也要为构造和析构 encrypted 付出代价。因此得出以下结论:您最好将 encrypted 的定义推迟到您确信您真的需要他的时候:

// 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 的时候没有任何初始化参数。这就意味着很多情况下将使用他的缺省构造函数,对于一个对象您首先应该做的就是给他一些值,这经常能够通过赋值来完成我已解释了为什么缺省构造(default-constructing)一个对象然后赋值给他比用您真正需要他持有的值初始化他更低效。那个分析也适用于此。例如,假设 encryptPassword 的核心部分是在这个函数中完成的:

void encrypt(std::string& s); // encrypts s in place

  那么,encryptPassword 就能够这样实现,即使他还不是最好的方法:

// 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;
}

  一个更可取得方法是用 password 初始化 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;
}

  这个建议就是本 Item 的标题中的“只要有可能(as long as possible)”的真正含义。您不但应该推迟一个变量的定义直到您不得不用他之前的最后一刻,而且应该试图推迟他的定义直到您得到了他的初始化参数。通过这样的做法,您能够避免构造和析构无用对象,而且还能够避免不必要的缺省构造。更进一步,通过在他们的含义已很明确的上下文中初始化他们,有助于对变量的作用文档化。

  “但是对于循环会如何?”您可能会有这样的疑问。假如一个变量仅仅在一个循环内使用,是循环外面定义他并在每次循环迭代时赋值给他更好一些,还是在循环内部定义这个变量更好一些呢?也就是说,下面这两个大致的结构中哪个更好一些?

// 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);
... ...
} }

  这里我将一个类型 string 的对象换成了一个类型 Widget 的对象,以避免对这个对象的构造、析构或赋值操作的成本的任何已有的预见。

  对于 Widget 的操作而言,就是下面这两个方法的成本:

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

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

  对于那些赋值的成本低于一个构造函数/析构函数对的成本的类,方法 A 通常更高效。特别是在 n 变得很大的情况下。否则,方法 B 可能更好一些。此外,方法 A 和方法 B 相比,使得名字 w 在一个较大的区域(包含循环的那个区域)内均可见,这可能会破坏程式的易理解性和可维护性。因此得出以下结论:除非您确信以下两点:(1)赋值比构造函数/析构函数对成本更低,而且(2)您正在涉及您的代码中的性能敏感的部分,否则,您应该默认使用方法 B。

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇: C 箴言:拷贝一个对象的任何组成部分

下一篇: 深入理解c# 3.0的五项主要改进_c#教程