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

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 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) 尽可能延迟变量定义的出现,这样做可以增加程序的清晰度和改善程序效率。

相关文章
|
1月前
|
存储 安全 C++
C++:指针引用普通变量适用场景
指针和引用都是C++提供的强大工具,它们在不同的场景下发挥着不可或缺的作用。了解两者的特点及适用场景,可以帮助开发者编写出更加高效、可读性更强的代码。在实际开发中,合理选择使用指针或引用是提高编程技巧的关键。
24 1
|
15天前
|
JavaScript 前端开发 Java
通过Gtest访问C++静态、私有、保护变量和方法
通过Gtest访问C++静态、私有、保护变量和方法
13 0
|
1月前
|
编译器 C++
【C/C++学习笔记】C++声明与定义以及头文件与源文件的用途
【C/C++学习笔记】C++声明与定义以及头文件与源文件的用途
29 0
|
2月前
|
编译器 C++ 运维
开发与运维函数问题之函数的返回类型如何解决
开发与运维函数问题之函数的返回类型如何解决
32 6
|
1月前
|
JSON Android开发 数据格式
Android c++ core guideline checker 应用问题之JSON compilation database的定义如何解决
Android c++ core guideline checker 应用问题之JSON compilation database的定义如何解决
|
2月前
|
机器学习/深度学习 PyTorch 算法框架/工具
C++多态崩溃问题之在PyTorch中,如何定义一个简单的线性回归模型
C++多态崩溃问题之在PyTorch中,如何定义一个简单的线性回归模型
|
3月前
|
安全 C++
C++一分钟之-互斥锁与条件变量
【6月更文挑战第26天】在C++并发编程中,`std::mutex`提供互斥访问,防止数据竞争,而`std::condition_variable`用于线程间的同步协调。通过`lock_guard`和`unique_lock`防止忘记解锁,避免死锁。条件变量需配合锁使用,确保在正确条件下唤醒线程,注意虚假唤醒和无条件通知。生产者-消费者模型展示了它们的应用。正确使用这些工具能解决同步问题,提升并发性能和可靠性。
46 4
|
3月前
|
程序员 编译器 C++
探索C++语言宝库:解锁基础知识与实用技能(类型变量+条件循环+函数模块+OOP+异常处理)
探索C++语言宝库:解锁基础知识与实用技能(类型变量+条件循环+函数模块+OOP+异常处理)
35 0
|
3月前
|
C++
C++数组(定义、遍历、长度、地址、最大值、逆置、冒泡排序)
C++数组(定义、遍历、长度、地址、最大值、逆置、冒泡排序)
|
3月前
|
C++
C++之变量与常量
C++之变量与常量