C++中尽可能延迟变量定义的时间_c


 

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) 尽可能延迟变量定义的出现,这样做可以增加程序的清晰度和改善程序效率。