#Charpter4 智能指针
诗人和作曲家喜欢写一些关于love的作品,也有可能写一些关于计数(counting)的作品,很少有两者兼顾的。总有些例外,如Elizabeth Barrett Browning:"How do I love thee? Let me count the ways",又如Paul Simon:"There must be 50 ways to leave your lover.",被这些诗句启发,我们来尝试列举下为什么原生指针(raw pointer)不那么讨人喜欢(love)的理由:
1.从它的声明看不出它指向的是一个单个的对象还是一个数组
2.当你使用完它的时候,从它的声明看不出来你是否应该把它销毁,例如,当指针拥有(owns)它当前指向的对象时
3.当你确定要销毁它指向的内容的时候,又要犯难了,因为你不知道要使用delete,还是要使用另外一个不同的销毁机制(如将该指针传递到一个指定的析构函数里)
4.当你终于要使用delete决定要销毁它了,因为第1条,你又不知道该使用delete还是delete[],因为一旦使用错误,结果会是不确定的
5.最后,你终于确定了指针指向的内容是啥了,也确定了改用什么样的方式来销毁;问题又来了,因为你不能保证在你的程序的每条路径中,你的销毁代码只执行一次,不执行的话会造成内存泄露,多执行哪怕一次会产生不确定的行为
6.目前没有方法来确定一个指针是悬挂指针,即确定一个指针不再拥有它指向的对象。当一个指针指向的对象被销毁了,该指针就变成了悬挂指针。
原生指针是一款很强大的工具,但是依据进数十年的经验,可以确定的一点是:稍有不慎,这个工具就会反噬它的使用者。
终于,来解决上述难题的智能指针出现了,智能指针表现起来很像原生指针,它相当于是原生指针的一层再包装(wrapper),但是规避了许多使用原生指针带来的陷阱。你应该尽量使用智能指针,它几乎能做到原生指针能做到的所有功能,却很少给你犯错的机会。
在C++11标准中规定了四个智能指针:std::auto_ptr, std::unique_ptr, std::shared_ptr, 以及std::weak_ptr.它们都用来设计辅助管理动态分配对象的生命周期,即,确保这些对象在正确的时间(包括发生异常时)用正确的方式进行回收,以确保不会产生内存泄露.
C++98尝试用std::auto_ptr来标准化后来成为C++11中的std::unique_ptr的行为,为了达到目标,move语法是不可少的,但是,C++98当时还没有move语法,所以做了个妥协方案:利用拷贝操作来模拟move.这导致了一些很让人吃惊的代码(如拷贝一个std::auto_ptr会将它设置为null!)和一些让使用者觉得沮丧的使用限制(不能在容器中使用std::auto_ptr)
std::unique_ptr做到了std::auto_ptr所能做到的所有事情,而且它的实现还更高效。
智能指针的API有着显著的区别,他们之间唯一共同的一点功能就是默认的构造方法。因为这种API详细的介绍满大街都是啊,所以我把重点放到了这些API介绍所没有的知识,如:值得注意的使用场景,运行性能分析等等。掌握这些信息你就不只会可以单单的使用它们,更是学会了如何有效的运用它们。
- 出版者的忠告
- 致谢
- 简介
- 第一章 类型推导
- 条款1:理解模板类型推导
- 条款2:理解auto类型推导
- 条款3:理解decltype
- 条款4:知道如何查看类型推导
- 第二章 auto关键字
- 条款5:优先使用auto而非显式类型声明
- 条款6:当auto推导出非预期类型时应当使用显式的类型初始化
- 第三章 使用现代C++
- 条款7:创建对象时区分()和{}
- 条款8:优先使用nullptr而不是0或者NULL
- 条款9:优先使用声明别名而不是typedef
- 条款10:优先使用作用域限制的enmu而不是无作用域的enum
- 条款11:优先使用delete关键字删除函数而不是private却又不实现的函数
- 条款12:使用override关键字声明覆盖的函数
- 条款13:优先使用const_iterator而不是iterator
- 条款14:使用noexcept修饰不想抛出异常的函数
- 条款15:尽可能的使用constexpr
- 条款16:保证const成员函数线程安全
- 条款17:理解特殊成员函数的生成
- 第四章 智能指针
- 条款18:使用std::unique_ptr管理独占资源
- 条款19:使用std::shared_ptr管理共享资源
- 条款20:在std::shared_ptr类似指针可以悬挂时使用std::weak_ptr
- 条款21:优先使用std::make_unique和std::make_shared而不是直接使用new
- 条款22:当使用Pimpl的时候在实现文件中定义特殊的成员函数
- 第五章 右值引用、移动语义和完美转发
- 条款23:理解std::move和std::forward
- 条款24:区分通用引用和右值引用
- 条款25:在右值引用上使用std::move 在通用引用上使用std::forward
- 条款26:避免在通用引用上重定义函数
- 条款27:熟悉通用引用上重定义函数的其他选择
- 条款28:理解引用折叠
- 条款29:假定移动操作不存在,不廉价,不使用
- 条款30:熟悉完美转发和失败的情况
- 第六章 Lambda表达式
- 条款31:避免默认的参数捕捉
- 条款32:使用init捕捉来移动对象到闭包
- 条款33:在auto&&参数上使用decltype当std::forward auto&&参数
- 条款34:优先使用lambda而不是std::bind
- 第七章 并发API
- 条款35:优先使用task-based而不是thread-based
- 条款36:当异步是必要的时声明std::launch::async
- 条款37:使得std::thread在所有的路径下无法join
- 条款38:注意线程句柄析构的行为
- 条款39:考虑在一次性事件通信上void的特性
- 条款40:在并发时使用std::atomic 在特殊内存上使用volatile
- 第八章 改进
- 条款41:考虑对拷贝参数按值传递移动廉价,那就尽量拷贝
- 条款42:考虑使用emplace代替insert