[Effective C++]条款26: 尽可能延后变量定义式出现的时间

131 阅读3分钟

每当定义一个变量时,就会带来构造和析构的运行成本。因为代码运行到定义时会调用对象的构造函数,当离开作用域时便会调用析构函数。即使我们不使用这个变量,仍需耗费这些成本。 或许你会认为, 不可能定义一个不使用的变量, 先看下面的例子: 定义了一个函数, 给密码进行加密返回加密后的密码, 并且要求输入的密码够长, 如果密码短的话会丢出一个异常(类型为logic_error, 定义于C++标准库, 见条款54).

// 这个函数过早的定义了encrypted
string encryptPassword(const string& password){
    using namespace std;
    string encrypted; 					

    if(password.length() < minPasswordLength)
        throw logic_error("Password too short");	//抛出异常
	
        ...	    // 必要动作, 产生一个加密的密码, 置于变量encrypted内
        
    return encrypted;
}

变量 encrypted 在此函数中并非完全未被使用,但如果有个异常被抛出,它就真的没被使用,但你仍需耗费它的构造和析构成本。因此我们最好推迟 encrypted 的定义,直到我们保证能够用到它:

// 延后encrypted定义, 直至真正的需要它
string encryptPassword(const string& password){
    using namespace std;
    if(password.length() < minPasswordLength)
        throw logic_error("Password too short"); // 抛出异常
    string encrypted;     // 调用了默认构造
    encrypted = password; // 调用了拷贝赋值运算符
    ...		          //必要动作,加密
        
    return encrypted;
}

但是这段代码仍然不够秾纤合度, 因为encrypted虽获定义却无任何实参作为初值, 这就意味着调用的是default构造函数. 可是很多时候我们对对象应该做的第一件事情就是给他个值, 这通常是通过一个赋值动作来达成. 在条款4中解释了为什么通过default构造函数构造出来一个对象然后对它赋值直接在构造函数指定初值效率差(前者先调用一次default构造函数, 再调用copy构造函数;后者只调用一次copy构造函数). 所以我们可以基于上面的代码块进一步优化即:

string encrypted;     // 调用了default构造 
encrypted = password; // 调用了copy赋值运算符 
//替换为 
string encrypted(password); //通过copy构造函数定义并初始化

这告诉了本条款中延迟定义的真正意义:你不仅要把变量定义推迟到非得使用该变量的前一刻为止,还要尝试推迟这份定义直到能够给它初值实参为止。这样可以不仅可以避免构造(和析构)非必要对象,还可以避免无意义的default构造。

那么在循环环境下该怎么办呢?如果变量在循环内使用,那么把它定义在循环外,还是循环内呢?

//A:在循环外面定义 
Widget w; 
for(int i=0; i<n, i++) 
{ 
    w=...; 
    ... 
} 

//B:在循环里面定义 
for(int i=0; i<n; i++)
{ 
    Widget w(...); 
    ... 
}

在 Widget 函数内部,以上两种写法的成本如下:

  • 做法 A :1个构造函数 + 1个析构函数 + n个赋值操作

  • 做法 B :n个构造函数 + n个析构函数 如果赋值成本 < (构造+析构) 成本的类,做法A是更高效的选择,尤其是当n很大的时候,反之做法B则是更好的选择。但是做法 A中的 w 的作用域比做法B要大,有时是不利于程序的可读性和可维护性的。因此除非你知道赋值成本 < (构造+析构)成本,而且这段代码要更注重效率,那么我们应该默认使用B。

  • Reference: blog.csdn.net/qq_34168988…