当前位置: 首页 > news >正文

Effective Modern C++ 完全解读笔记汇总

Effective Modern C++ 完全解读笔记汇总

Effective Modern C++ 学习和解读笔记汇总于此。

Chapter 1. Deducing Types

Item 1: Understand template type deduction.

  • 在模板类型推导中,引用类型参数将被视为非引用类型处理,也就是说其引用性被忽略。
  • 在万能引用参数类型推导时,左值参数被特殊处理。
  • 值传递形参的类型推导时,其 constvolatile 被忽略。
  • 在模板类型推导时,数组或者函数类型被转换为指针类型,除非它们用来初始化引用。

Item 2: Understand auto type deduction.

  • auto 类型推导除了大括号初始化列表方式外,和模板类型推导方法一致。模板类型推导不支持 std::initializer_list
  • 函数返回值为 auto 时,实际是使用模板推导,不是 auto 类型推导。

Item 3: Understand decltype.

  • decltype几乎总是能够得出变量或者表达式的类型。
  • 对于类型为 T 的左值表达式,而不是名字,decltype 基本上总是输出 T&
  • C++14支持 delctype(auto),像是 auto,能够自动从初始化列表中推断出类型,但它使用的是decltype 的推断规则。

Item 4: Know how to view deduced types.

  • 通常可以使用 IDE 编辑器、编译器报错信息和 Boost TypeIndex 库来查看已推断的类型。
  • 一些工具的结果可能没有帮助或者不准确,还是要理解透彻 C++ 的类型推断规则。

Chapter 2. auto

Item 5: Prefer auto to explicit type declarations.

  • auto 变量必须初始化,不受类型不匹配导致移植和效率问题。
  • auto 类型也受 Item2 和 Item6 中介绍的陷阱困扰。

Item 6: Use the explicitly typed initializer idiom when auto deduces undesired types.

  • 隐式的代理类型可能导致 auto 类型推导结果不符合预期。
  • 对于这种代理类型一般使用显式类型申明或者显示类型初始化。

Chapter 3. Moving to Modern C++

Item 7: Distinguish between () and {} when creating objects.

  • 括号初始化是最广泛使用的初始化语法,它防止窄化转换,并且对于 C++ 最令人头疼的解析有天生的免疫性。
  • 在构造函数重载决议中,括号初始化尽最大可能与 std::initializer_list 参数匹配,即便其他构造函数看起来是更好的选择。
  • 对于数值类型的std::vector来说使用花括号初始化和圆括号初始化会产生巨大的不同。
  • 在模板类选择使用小括号初始化或使用花括号初始化创建对象是一个挑战。

Item 8: Prefer nullptr to 0 and NULL.

  • 相较于 0 和 NULL,优先使用 nullptr
  • 避免对整数类型和指针类型的重载。

Item 9: Prefer alias declarations to typedefs.

  • typedef 不支持模板化,但是别名声明支持。
  • 模板别名没有 ::type 后缀,在模板中,typedef 还经常要求使用 typename 前缀。
  • C++14C++11 中的类型特征转换提供了模板别名。

Item 10: Prefer scoped enums to unscoped enums.

  • C++98 风格的 enumunscoped enum
  • scoped enums 的枚举成员仅仅对枚举体内部可见。只能通过类型转换( cast )转换为其他类型。
  • scopded enumsunscoped enum 都支持指定潜在类型。scoped enum 默认潜在类型是 intunscoped enum 没有默认的潜在类型。
  • scoped enum 总是可以前置声明的。unscoped enum 只有当指定潜在类型时才可以前置声明。

Item 11: Prefer deleted functions to private undefined ones.

  • 比起声明函数为 private 但不定义,使用 delete 函数更好。
  • 任何函数都能 delete ,包括非成员函数和模板实例。

Item 12: Declare overriding functions override.

  • 为重载函数加上override。
  • 成员函数引用限定让我们可以区别对待左值对象和右值对象(即*this)。

Item 13: Prefer const_iterators to iterators.

  • 优先考虑 const_iterator 而非 iterator。
  • 在最大程度通用的代码中,优先考虑非成员函数版本的 begin、end、rbegin 等,而非同名成员函数。

Item14: Declare functions noexcept if they won‘t emit exception.

  • noexcept 是函数接口的一部分,并且调用者可能会依赖这个接口。
  • 相较于 non-noexcept 函数,noexcept 函数有被更好优化的机会。
  • noexcept 对于 move 操作、swap、内存释放函数和析构函数是非常有价值的。
  • 大部分函数是异常中立的而不是 noexcept。

Item 15: Use constexpr whenever possible.

  • constexpr 对象是 const 的,给它初始化的值需要在编译时知道。
  • 如果使用在编译时就知道的参数来调用 constexpr 函数,它就能产生编译时的结果。
  • 相较于 non-constexpr 对象和函数,constexpr 对象很函数能被用在更广泛的上下文中。
  • constexpr 是对象接口或函数接口的一部分。

Item 16: Make const member functions thread safe.

  • 让 const 成员函数做到线程安全,除非你确保它们永远不会用在多线程的环境下。
  • 相比于 std::mutex,使用 std::atomic 变量能提供更好的性能,但是它只适合处理单一的变量或内存单元。
  • std::mutex 和 std::atomic 都是 move-only 的。

Item 17: Understand special member function generation.

  • 特殊成员函数是那些编译器可能自己帮我们生成的函数:默认构造函数,析构函数,copy 操作,move 操作。
  • 只有在类中没有显式声明的 move 操作,copy 操作和析构函数时,move 操作才被自动生成。
  • 只有在类中没有显式声明的拷贝构造函数的时候,拷贝构造函数才被自动生成。只要存在 move 操作的声明,拷贝构造函数就会被删除(delete)。拷贝 operator= 和拷贝构造函数的情况类似。在有显式声明的 copy 操作或析构函数时,另一个 copy 操作能被生成,但是这种生成方法是被弃用的。
  • 成员函数模板永远不会抑制特殊成员函数的生成。

Chapter 4. Smart Pointers

Item 18: Use std::unique_ptr for exclusive-ownership resource management.

  • std::unique_ptr 是一个小的、快的、move-only 的智能指针,它能用来管理资源,并且独占资源的所有权。
  • 默认情况下,std::unique_ptr 资源的销毁是用 delete 进行的,但也可以用户自定义 deleter。用带状态的 deleter 和函数指针作为 deleter 会增加 std::unique_ptr 对象的大小。
  • 很容易将 std::unique_ptr 转换为 std::shared_ptr。

Item 19: Use std::shared_ptr for shared-ownership resource management.

  • std::shared_ptr 为任意共享所有权的资源提供一种自动垃圾回收的便捷方式。
  • 较之于 std::unique_ptr,std::shared_ptr 对象占用的内存通常大两倍,控制块会产生开销,需要原子引用计数修改操作。
  • 默认资源销毁是通过 delete,但是也支持自定义 deleter。自定义 deleter 的类型对 std::shared_ptr 的类型没有影响。
  • 避免从原始指针变量上创建 std::shared_ptr。

Item 20: Use std::weak_ptr for std::shared_ptr like pointers that can dangle.

  • 对类似 std::shared_ptr 可能悬空的指针,使用 std::weak_ptr。
  • std::weak_ptr 的潜在使用场景包括:caching、observer lists、避免 std::shared_ptr 的循环引用。

Item 21: Prefer std::make_unique and std::make_shared to direct use of new.

  • 和直接使用 new 相比,make 函数消除了代码重复、提高了异常安全性。对于 std::make_shared和 std::allocate_shared,生成的代码更小更快。
  • 不适合使用 make 函数的情况包括需要指定自定义删除器和希望用大括号初始化。
  • 对于std::shared_ptrs, make函数可能不被建议的其他情况包括 (1)有自定义内存管理的类和 (2)特别关注内存的系统、非常大的对象,以及 std::weak_ptrs 比对应的 std::shared_ptrs 存在的时间更长。

Item 22: When using the Pimpl Idiom, define special member functions in the implementation file.

  • pImpl 惯用法通过减少类实现和类使用者之间的编译依赖来减少编译时间。
  • 对于std::unique_ptr 类型的 pImpl 指针,需要在头文件的类里声明特殊的成员函数,但是在实现文件里面来实现他们。即使是编译器自动生成的代码可以工作,也要这么做。
  • 以上的建议只适用于 std::unique_ptr,不适用于 std::shared_ptr。

Chapter 5. Rvalue References, Move Semantics, and Perfect Forwarding

Item 23: Understand std::move and std::forward.

  • std::move 无条件将输入转化为右值。它本身并不移动任何东西。
  • std::forward 把其参数转换为右值,仅仅在参数被绑定到一个右值时。
  • std::move 和 std::forward 只是做类型转换,在运行时(runtime)不做任何事。

Item 24: Distinguish universal references from rvalue references.

  • 如果一个函数模板参数有 T&& 格式,并且发生类型推导,或者一个对象使用 auto&& 来声明,那么参数或对象就是一个万能引用。
  • 如果类型推导的格式不是准确的 T&&(type&&),或者如果类型推导没有发生,T&&(type&&)就是一个右值引用。
  • 如果用右值来初始化,万能引用相当于右值引用。如果用左值来初始化,则相当于左值引用。

Item 25: Use std::move on rvalue references, std::forward on universal references.

  • 对右值引用使用 std::move,对通用引用使用 std::forward。
  • 对按值返回的函数返回值,无论返回右值引用还是通用引用,执行相同的操作。
  • 当局部变量就是返回值是,不要使用s td::move 或者 std::forward。

Item 26: Avoid overloading on universal references.

  • 对万能引用参数的函数进行重载,调用机会将比你期望的多得多。
  • 完美转发构造函数是糟糕的实现,因为对于 non-const 左值不会调用拷贝构造而是完美转发构造,而且会劫持派生类对于基类的拷贝和移动构造的调用。

Item 27: Familiarize yourself with alternatives to overloading on universal references.

  • 万能引用和重载的组合替代方案包括使用不同的函数名、通过 const 左值引用传参、按值传递参数,使用 tag 分发。
  • 通过 std::enable_if 约束模板来允许万能引用和重载组合使用,std::enable_if 可以控制编译器什么条件才使用万能引用的实例。
  • 万能引用参数通常具有高效率的优势,但通常可用性较差。

Item 28: Understand reference collapsing.

  • 引用折叠发生在四种情况:模板实例化,auto 类型的生成,创建和使用 typedef、别名声明和decltype。
  • 当编译器生成了引用的引用时,通过引用折叠就是单个引用。其中之一为左值引用就是左值引用,否则就是右值引用。
  • 在类型推导区分左值和右值以及引用折叠发生的上下文中,万能引用是右值引用。

Item 29: Assume that move operations are not present, not cheap, and not used.

  • 假设移动操作不可用、不廉价。
  • 在已知类型或支持移动语义的代码中,不需要进行此假设。

Item 30: Familiarize yourself with perfect forwarding failure cases.

  • 当模板类型推导失败或者推导类型是错误的时候完美转发会失败。
  • 导致完美转发失败的类型有花括号初始化、空指针的 0 或者 NULL、只声明的整型 static const 数据成、,模板和重载的函数名和位域。

Chapter 6. Lambda Expressions

Item 31: Avoid default capture modes.

  • 默认的按引用捕获可能会导致引用悬挂。
  • 默认的按值引用对于悬挂指针很敏感(尤其是this指针),并且它会误导人认为 lambda 是独立的。

Item 32: Use init capture to move objects into closures.

  • C++14 使用初始化捕获模式实现移动捕获。
  • C++11 使用 std::bind 间接实现移动捕获。

Item 33: Use decltype on auto&& parameters to std::forward them.

  • 对 auto&& 参数使用 decltype来转发(std::forward)。

Item 34: Prefer lambdas to std::bind.

  • 相较于 std::bind,lambda 代码可读性更强、更容易理解、性能可能更好。
  • C++11 的 std::bind 在实现移动捕获、模板函数对象方面可以弥补 lambda 的不足。

Chapter 7. The Concurrency API

Item 35: Prefer task-based programming to thread-based.

  • std::thread API 不能直接访问异步函数执行的结果,如果执行函数有异常抛出,代码终止执行。
  • 基于线程的编程方式存在资源耗尽、认购超额、负载均衡的方案移植性不佳。
  • 通过 std::async 的基于任务的编程方式会默认解决上面的问题。

Item 36: Specify std::launch::async if asynchronicity is essential.

  • std::async 的默认启动策略允许是异和者同步。
  • 灵活性导致访问 thread_locals 的不确定性,隐含了任务可能不会被执行的含义,会影响程序基于超时的 wait 调用。
  • 只有确定是异步时才指定为 std::launch::async。

Item 37: Make std::threads unjoinable on all paths.

  • 在所有路径上保证 thread 是 unjoinable 的。
  • 析构时 join 会导致难以调试的性能异常问题。
  • 析构时 detach 会导致难以调试的未定义行为。
  • 在成员列表的最后声明 std::thread 类型成员。

Item 38: Be aware of varying thread handle destructor behavior.

  • future 的正常析构行为只是销毁 future 本身的成员数据。
  • 最后一个引用通过 std::async 创建的 non-deferred 任务的共享状态的 future 会阻塞到任务结束。

Item 39: Consider void futures for one-shot event communication.

  • 对于简单的事件通信,基于条件变量的方法需要一个多余的互斥锁、对检测和反应任务的相对进度有约束,并且需要反应任务来确认事件是否已发生。
  • 基于 flag 的方法可以避免的上一条的问题,但是不是真正的阻塞任务。
  • 组合条件变量和 flag 使用,上面的问题都解决了,但是逻辑让人多少有点感觉有点生硬。
  • 使用 std::promise 和 future 的方案可以避免这些问题,但为共享状态使用了堆内存,并且仅限于一次性通信。

Item 40: Use std::atomic for concurrency, volatile for special memory.

  • std::atomic 用于不使用锁的多线程数据访问,用于编写并发程序。
  • volatile 阻止内存的读写优化。用于特殊内存的场景。

Chapter 8. Tweaks

Item 41: Consider pass by value for copyable parameters that are cheap to move and always copied.

  • 对于可复制、移动开销低、且无条件复制的参数,按值传递效率基本与按引用传递效率一致,而且易于实现,生成更少的目标代码。
  • 通过构造函数拷贝参数可能比通过赋值拷贝开销大的多。
  • 按值传递会引起切片问题,不适合基类类型的参数。

Item 42: Consider emplacement instead of insertion.

  • 原则上,emplacement 函数会比传统插入函数更高效。
  • 实际上,当执行如下操作时,emplacement 函数更快:(1)值被构造到容器中,而不是直接赋值;(2)传入参数的类型与容器类型不一致;(3)容器不拒绝已经存在的重复值。
  • emplacement 函数可能执行类型转化,而传统插入函数会拒绝。

相关文章:

  • 渗透测试——命令执行漏洞(RCE)详解
  • C | 浮点型数据在内存中的存储方式
  • NVMe系列专题之六:电源管理
  • 【DS】链表的介绍和实现(单/双链表)
  • 百度测试实习小一面
  • RK3399平台开发系列讲解(GPIO子系统)4.3、详解GPIO子系统
  • Oracle Form - 文件夹数据块点击排序后只剩一行显示(已解决)
  • 【操作系统】面向真题学操作系统 —— 操作系统概述客观题
  • QT-子窗口关闭时,为什么不执行析构函数问题
  • 猿创征文| openGauss 数据库实战 主备高可用部署(主备部署模式)
  • 基于simulink的电气弹簧ES稳压控制模型仿真
  • Day759.Redis脑裂问题 -Redis 核心技术与实战
  • CSS高级篇——更多选择器 (selectors)
  • R语言(5) 折线图、散点图
  • 云原生之容器编排实践-SpringBoot应用以YAML描述文件部署pod到minikube
  • 【从零开始安装kubernetes-1.7.3】2.flannel、docker以及Harbor的配置以及作用
  • Android单元测试 - 几个重要问题
  • CAP理论的例子讲解
  • Consul Config 使用Git做版本控制的实现
  • go语言学习初探(一)
  • Java教程_软件开发基础
  • Js基础知识(一) - 变量
  • js算法-归并排序(merge_sort)
  • k8s如何管理Pod
  • magento2项目上线注意事项
  • MD5加密原理解析及OC版原理实现
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • RedisSerializer之JdkSerializationRedisSerializer分析
  • Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比...
  • 缓存与缓冲
  • 坑!为什么View.startAnimation不起作用?
  • 离散点最小(凸)包围边界查找
  • 浏览器缓存机制分析
  • 设计模式 开闭原则
  • 十年未变!安全,谁之责?(下)
  • 思维导图—你不知道的JavaScript中卷
  • elasticsearch-head插件安装
  • linux 淘宝开源监控工具tsar
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • #if和#ifdef区别
  • #include到底该写在哪
  • #QT项目实战(天气预报)
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (11)MSP430F5529 定时器B
  • (3)nginx 配置(nginx.conf)
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (三)docker:Dockerfile构建容器运行jar包
  • (十五)Flask覆写wsgi_app函数实现自定义中间件
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (未解决)jmeter报错之“请在微信客户端打开链接”
  • (转) Face-Resources
  • (转)http-server应用
  • .bat批处理(六):替换字符串中匹配的子串