1.为什么要延迟变量定义时间
只要你定义了一个变量而变量的类型带有一个构造函数和析构函数,那么当程序的控制流到达这个变量的定义式时,你就要花费构造成本。同样的,当这个变量离开其作用域时,你就要花费析构成本。即使这个变量最终并未使用,但仍然需要耗费这些成本,所以应该尽可能避免这种情形。
或许你会认为,在程序中你不可能定义一个不使用的变量。但是,在程序中恰恰会出现这种情况,如下面的程序所示:
const int MinimumPasswordLength = 6; std::string encryptPassword(const std::string& password){ std::string encrypted; // 过早的定义了变量encrypted if(password.length() < MinimumPasswordLength){ throw std::logic_error("Password is too short"); } // ... 必要的动作,将一个加密后的密码置于变量encrypted中 return encrypted; }
变量encrypted在函数encryptPassword()中并非完全未使用,但如果有个异常抛出,它就真的没被用过。也就是说如果函数encryptPassword()抛出异常,你就要付出encrypted的构造成本和析构成本。因此,最好延迟encrypted的定义式,如下面改进后的代码段所示:
const int MinimumPasswordLength = 6; // 这个函数延迟encrypted的定义,直到真正需要它时才定义 std::string encryptPassword(const std::string& password){ if(password.length() < MinimumPasswordLength){ throw std::logic_error("Password is too short"); } std::string encrypted; // 延迟定义了变量encrypted // ... 必要的动作,将一个加密后的密码置于变量encrypted中 return encrypted; }
2.考虑效率问题
但是,上面的代码还不够完美。因为变量encrypted虽然定义却无任何实参作为初值,这意味着调用的是默认构造函数。很多情况下,你对变量做的第一件事就是给它一个初值,通常是通过一个赋值动作完成的。前面的文章确定对象使用前已先被初始化中已经解释了为什么“通过默认构造函数构造出的一个对象然后对它赋值”比“直接在构造时指定初值”效率差。如下面的代码段所示:
const int MinimumPasswordLength = 6; // 对s进行加密操作的函数 void encrypt(std::string& s); // 这个函数延迟encrypted的定义,直到真正需要它时才定义。但是,此函数仍然有着效率低的问题 std::string encryptPassword(const std::string& password){ if(password.length() < MinimumPasswordLength){ throw std::logic_error("Password is too short"); } std::string encrypted; // 调用默认构造函数 encrypted = password; // 赋值给encrypted encrypt(encrypted); // 调用前面声明的函数encrypt() return encrypted; }
更合适的方法是以password作为变量encrypted的初始值,跳过无意义的默认构造过程,如下面的代码段所示:
const int MinimumPasswordLength = 6; // 对s进行加密操作的函数 void encrypt(std::string& s); // 这个函数延迟encrypted的定义,直到真正需要它时才定义。 std::string encryptPassword(const std::string& password){ if(password.length() < MinimumPasswordLength){ throw std::logic_error("Password is too short"); } std::string encrypted(password); // 直接在构造时指定初值 encrypt(encrypted); // 调用前面声明的函数encrypt() return encrypted; }
3.延迟变量定义的其他场合
我们不应该只延迟变量的定义,直到非要使用该变量的前一刻为止。甚至应该尝试延迟变量定义直到能够给它初值实参为止。如果这样,不仅能避免构造与析构非必要的对象,还可以避免无意义的默认构造行为。
但是,遇到“循环该怎么办”呢?如果变量只在循环体内部使用,那么把它定义在循环外并在每次循环迭代时赋值给它比较好,还是该把它定义在循环体的内部?如下面的代码段所示:
class Widget { }; // 方法1:定义在循环体外 Widget w; for(int i = 0; i < n; i++){ w = 取决于i的某个值; // ... } // 方法2:定义在循环体内 for(int i = 0; i < n; i++){ Widget w(取决于i的某个值); // ... }
在Widget函数内部,以上两种写法的成本如下所示:
(1) 方法1:1个构造函数+1个析构函数 + n个赋值操作
(2) 方法2:n个构造函数 + n个析构函数
如果类的一个赋值成本低于一组构造+析构成本,方法1大体而言比较高效。尤其当n值很大时,否则方法2比较好。此外,方法1造成名称w的作用域(覆盖整个循环)比方法2更大,有时候这对程序的可理解性和易维护性造成冲击。因此,除非你知道赋值成本比“构造+析构”的成本低;你正在处理代码中效率高度敏感的部分。否则,你应该使用方法2。
4.总结
(1) 尽可能延迟变量定义的出现,这样做可以增加程序的清晰度和改善程序效率。