读书笔记 effective c++ Item 26 尽量推迟变量的定义

简介: 1. 定义变量会引发构造和析构开销 每当你定义一种类型的变量时:当控制流到达变量的定义点时,你引入了调用构造函数的开销,当离开变量的作用域之后,你引入了调用析构函数的开销。对未使用到的变量同样会产生开销,因此对这种定义要尽可能的避免。

1. 定义变量会引发构造和析构开销

每当你定义一种类型的变量时:当控制流到达变量的定义点时,你引入了调用构造函数的开销,当离开变量的作用域之后,你引入了调用析构函数的开销。对未使用到的变量同样会产生开销,因此对这种定义要尽可能的避免。

2. 普通函数中的变量定义推迟

2.1 变量有可能不会被使用到的例子

你可能会想你永远不会定义未使用的变量,你可能要再考虑考虑。看下面的函数,此函数返回password的加密版本,提供的password需要足够长。如果password太短,函数会抛出一个logic_error类型的异常,此异常被定义在标准C++库中(Item 54):

 1 // this function defines the variable "encrypted" too soon
 2 
 3 std::string encryptPassword(const std::string& password)
 4 
 5 {
 6 
 7 using namespace std;
 8 
 9 string encrypted;
10 
11 if (password.length() < MinimumPasswordLength) {
12 
13 throw logic_error("Password is too short");
14 
15 }
16 
17 ... // do whatever is necessary to place an
18 
19 // encrypted version of password in encrypted
20 
21 return encrypted;
22 
23 }

 

对象encrypted不是完全不会被用到,但是如果抛出了异常它就肯定不会被用到。这就是说,如果encryptPassword抛出了异常,你不会用到encrypted,但是你同样会为encrypted的构造函数和析构函数买单。因此,最好推迟encrypted的定义直到你认为你会使用它:

 1 // this function postpones encrypted’s definition until it’s truly necessary
 2 
 3 std::string encryptPassword(const std::string& password)
 4 
 5 {
 6 
 7 using namespace std;
 8 
 9 if (password.length() < MinimumPasswordLength) {
10 
11 throw logic_error("Password is too short");
12 
13 }
14 
15 string encrypted;
16 
17 ... // do whatever is necessary to place an
18 
19 // encrypted version of password in encrypted
20 
21 return encrypted;
22 
23 }

 

2.2 推迟变量定义的一种方法

 

上面的代码看起来还是不够紧凑,因为encrypted定义时没有带任何初始化参数。也就意味着默认构造函数会被调用。在许多情况下,你对一个对象做的第一件事就是给它提供一些值,这通常通过赋值来进行。Item 4解释了为什么默认构造一个对象紧接着对其进行赋值要比用一个值对其初始化效率要低。其中的分析在这里同样适用。举个例子,假设encryptPassword函数的最困难的部分在下面的函数中执行:

1 void encrypt(std::string& s); // encrypts s in place

 

然后encryptPassword可以像下面这样实现,虽然这可能不是最好的方法:

 1 // this function postpones encrypted’s definition until
 2 
 3 // it’s necessary, but it’s still needlessly inefficient
 4 
 5 std::string encryptPassword(const std::string& password)
 6 
 7 {
 8 
 9 ... // import std and check length as above
10 
11 string encrypted; // default-construct encrypted
12 
13 encrypted = password; // assign to encrypted
14 
15 encrypt(encrypted);
16 
17 return encrypted;
18 
19 }

 

2.2 推迟变量定义的更好方法

 

一个更好的方法是用password来初始化encypted,这样就跳过了无意义的和可能昂贵的默认构造函数:

 1 // finally, the best way to define and initialize encrypted
 2 
 3 std::string encryptPassword(const std::string& password)
 4 
 5 {
 6 
 7 ... // import std and check length
 8 
 9 string encrypted(password); // define and initialize via copy
10 
11 // constructor
12 
13 encrypt(encrypted);
14 
15 return encrypted;
16 
17 }

 

2.3 推迟变量定义的真正含义

这个建议是这个条款的标题中的“尽量推迟”的真正含义。你不但要将变量的定义推迟到你必须使用的时候,你同样应该尝试将定义推迟到你获得变量的初始化值的时候。这么做,你就能避免不必要的构造和析构,也避免了不必要的默认构造函数。并且,通过在意义已经明确的上下文中对变量进行初始化,你也帮助指明了使用此变量的意图

3. 如何处理循环中的变量定义

这时候你该想了:循环该怎么处理呢?如果一个变量只在一个循环中被使用,是将将变量定义在循环外,每次循环迭代为其赋值好呢?还是将其定义在循环内部好呢?也即是下面的结构哪个好?

 1 // Approach A: define outside loop 
 2 
 3 Widget w;
 4 
 5 for (int i = 0; i < n; ++i) { 
 6 
 7 w = some value dependent on i; 
 8 
 9 ... 
10 
11 } 
12 
13 
14 
15 // Approach B: define inside loop
16 
17 for (int i = 0; i < n; ++i) {
18 
19 Widget w(some value dependent oni);
20 
21 ...
22 
23 }

 

这里我用一个Widget类型的对象来替换string类型的对象,以避免对执行构造函数,析构函数或者赋值运算符的开销有任何偏见。

对于Widget来说,两种方法的开销如下:

  • 方法一: 1个构造函数+1个析构函数+n个赋值运算
  • 方法二:n个构造函数和n个析构函数

如果赋值运算的开销比一对构造函数/析构函数要小,方法A更加高效。尤其是在n很大的时候。否则,方法B要更高效。并且方法A比方法B使变量w在更大的范围内可见,这一点违反了程序的可理解性和可操作性。因此,除非你遇到下面两点:(1)赋值比构造/析构开销要小(2)你正在处理对性能敏感的代码。否则你应该默认使用方法B。


作者: HarlanC

博客地址: http://www.cnblogs.com/harlanc/
个人博客: http://www.harlancn.me/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接

如果觉的博主写的可以,收到您的赞会是很大的动力,如果您觉的不好,您可以投反对票,但麻烦您留言写下问题在哪里,这样才能共同进步。谢谢!

目录
相关文章
|
10月前
|
存储 C++
C++语言中指针变量int和取值操作ptr详细说明。
总结起来,在 C++ 中正确理解和运用 int 类型地址及其相关取值、设定等操纵至关重要且基础性强:定义 int 类型 pointer 需加星号;初始化 pointer 需配合 & 取址;读写 pointer 执向之处需配合 * 解引用操纵进行。
733 12
|
编译器 C++
《Effective C++ 改善程序与设计的55个具体做法》 第一章 笔记
《Effective C++ 改善程序与设计的55个具体做法》 第一章 笔记
234 0
|
JavaScript 前端开发 Java
通过Gtest访问C++静态、私有、保护变量和方法
通过Gtest访问C++静态、私有、保护变量和方法
478 1
|
存储 安全 C++
C++:指针引用普通变量适用场景
指针和引用都是C++提供的强大工具,它们在不同的场景下发挥着不可或缺的作用。了解两者的特点及适用场景,可以帮助开发者编写出更加高效、可读性更强的代码。在实际开发中,合理选择使用指针或引用是提高编程技巧的关键。
276 1
|
C语言 C++
实现两个变量值的互换[C语言和C++的区别]
实现两个变量值的互换[C语言和C++的区别]
303 0
|
安全 C++
C++一分钟之-互斥锁与条件变量
【6月更文挑战第26天】在C++并发编程中,`std::mutex`提供互斥访问,防止数据竞争,而`std::condition_variable`用于线程间的同步协调。通过`lock_guard`和`unique_lock`防止忘记解锁,避免死锁。条件变量需配合锁使用,确保在正确条件下唤醒线程,注意虚假唤醒和无条件通知。生产者-消费者模型展示了它们的应用。正确使用这些工具能解决同步问题,提升并发性能和可靠性。
377 4
|
存储 C++ 容器
C++一分钟之-变量与数据类型入门
【6月更文挑战第18天】**C++编程基础:变量与数据类型概览** 了解变量(存储数据的容器)和数据类型是编程入门的关键。声明变量如`int age = 25;`,注意初始化和类型匹配。基本数据类型包括整型(int等)、浮点型(float、double)、字符型(char)和布尔型(bool)。理解类型范围和精度,使用字面量后缀增强可读性。深入学习数组、指针、结构体和类,以及动态内存管理,避免数组越界和内存泄漏。不断实践以巩固理论知识。
227 1
|
程序员 编译器 C++
探索C++语言宝库:解锁基础知识与实用技能(类型变量+条件循环+函数模块+OOP+异常处理)
探索C++语言宝库:解锁基础知识与实用技能(类型变量+条件循环+函数模块+OOP+异常处理)
172 0
C++之变量与常量
C++之变量与常量
165 0
|
编译器 C++
《Effective C++ 改善程序与设计的55个具体做法》 第二章 构造/析构/赋值运算 笔记
《Effective C++ 改善程序与设计的55个具体做法》 第二章 构造/析构/赋值运算 笔记
205 0