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

2016-02-19 13:26 5 1 收藏

最近很多朋友喜欢上设计,但是大家却不知道如何去做,别担心有图老师给你解答,史上最全最棒的详细解说让你一看就懂。

【 tulaoshi.com - 编程语言 】


  在极大程度上,为你的类(包括类模板)和函数(包括函数模板)提供正确的定义是战斗的要害性部分。一旦你得到正确的结果,相应的实现很大程度上就是直截了当的。但是仍然有一些注重事项需要当心。过早地定义变量会对性能产生拖累。过度使用强制转换会导致缓慢的,难以维护的,被微妙的 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。
  
  Things to Remember
  
  ·只要有可能就推迟变量定义。这样可以增加程序的清楚度并提高程序的性能。
  

来源:http://www.tulaoshi.com/n/20160219/1604250.html

延伸阅读
很多企业在发展过程中,都有过几套治理软件共用的经历,往往造成在企业内部形成信息孤岛,无法统一治理的窘境 !-- frame contents -- !-- /frame contents -- 中冀集团自几年前开始信息化尝试之后,一共使用了4套系统,对梳理企业治理流程功劳不小,本来不必再使用集成化企业治理软件,但几套系统各自为政造成了集团内部的信息孤岛...
我谈到让一个类支持隐式类型转换通常是一个不好的主意。当然,这条规则有一些例外,最普通的一种就是在创建数值类型时。例如,假如你设计一个用来表现有理数的类,答应从整数到有理数的隐式转换看上去并非不合理。这的确不比 C++ 的内建类型从 int 到 double 的转换更不合理(而且比 C++ 的内建类型从 double 到 int 的转换合理得多)。在...
缺省情况下,C++ 以传值方式将对象传入或传出函数(这是一个从 C 继续来的特性)。除非你非凡指定其它方式,否则函数的参数就会以实际参数(actual argument)的拷贝进行初始化,而函数的调用者会收到函数返回值的一个拷贝。这个拷贝由对象的拷贝构造函数生成。这就使得传值(pass-by-value)成为一个代价不菲的操作。例如,考虑下面这个类...
在C#中定义常量的方式有两种,一种叫做静态常量(Compile-time constant),另一种叫做动态常量(Runtime constant)。前者用const来定义,后者用readonly来定义。 对于静态常量(Compile-time constant),它的书写方式如下: public const int MAX_VALUE = 10; 为什么称它为静态常量呢,因为如上声明可以按照如下理解(注...
在上一篇文章中介绍了作为资源治理类支柱的 Resource Acquisition Is Initialization (RAII) 原则,并描述了 auto_ptr 和 tr1::shared_ptr 在基于堆的资源上运用这一原则的表现。并非所有的资源都是基于堆的,然而,对于这样的资源,像 auto_ptr 和 tr1::shared_ptr 这样的智能指针通常就不像 resource handlers(资源治理者)那样合适。在...

经验教程

792

收藏

82
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部