书库技术与未来Effective Modern C++
书籍封面

Effective Modern C++

作者 Scott Meyers
20.0 分钟

摘要

《Effective Modern C++》总结

  • 总结:本书深入探讨了现代C++(C++11/14)的高效使用方法,包括类型推导、智能指针、移动语义、Lambda表达式和并发API等关键特性。
  • 你能获得:
    • 掌握现代C++的核心技术,编写更高效、更安全的代码
    • 避免常见陷阱,提升代码质量和可维护性
    • 了解高级C++特性的底层原理,为未来的学习打下坚实基础

核心内容:

1. 类型推导:理解模板、auto和decltype的不同规则

  • 详细解释:
    • 模板类型推导:涉及ParamType是指针/引用、通用引用或非指针/引用时的不同推导方式。引用会被忽略,通用引用特殊处理左值,传值时const/volatile会被忽略。
    • auto类型推导:与模板类型推导类似,但花括号初始化会推导为std::initializer_list,C++14在函数返回值/Lambda参数中使用auto时,遵循模板类型推导规则。
    • decltype:直接返回表达式类型,但非变量名的左值表达式会产生左值引用。C++14的decltype(auto)使用decltype规则推导类型。
  • 举例:std::vector::reference代理类导致auto推导错误类型,用static_cast显式转换。
  • 行动建议:掌握类型推导规则,使用IDE或编译信息查看推导结果,必要时使用显式类型初始化。

2. auto:优先使用auto,但避免类型推导陷阱

  • 详细解释:
    • auto简化代码,避免类型不匹配和移植性问题。
    • auto变量必须初始化,可用于存储只有编译器知道的类型。
    • 显式类型初始化惯用法:当auto推导出错误类型时,用static_cast强制转换。
  • 举例:避免std::vector::reference,std::pair类型错误,以及代理类问题。
  • 行动建议:在可读性允许的情况下,优先使用auto,但注意不可见代理类和花括号初始化的陷阱。

3. decltype:准确获取表达式类型,谨慎使用decltype(auto)

  • 详细解释:
    • decltype返回表达式的精确类型,可用于声明依赖于形参类型的函数模板返回值。
    • decltype((x))与decltype(x)不同,前者总是返回引用。
    • decltype(auto)结合了auto的类型推导和decltype的规则。
  • 举例:使用decltype(auto)实现authAndAccess函数,返回容器operator[]的正确类型。
  • 行动建议:熟悉decltype的规则,特别是decltype((x)),谨慎使用decltype(auto),确保类型推导结果符合预期。

4. 智能指针:使用std::unique_ptr管理独占资源,std::shared_ptr管理共享资源

  • 详细解释:
    • std::unique_ptr:独占所有权,小巧高效,支持自定义删除器,可转换为std::shared_ptr。适用于工厂函数返回类型和Pimpl惯用法。
    • std::shared_ptr:共享所有权,自动垃圾回收,支持自定义删除器,存在控制块开销和循环引用问题。
    • std::weak_ptr:不参与所有权共享,用于观察std::shared_ptr指向的对象是否已被销毁。
  • 举例:使用std::unique_ptr实现makeInvestment工厂函数,使用std::weak_ptr实现缓存和观察者模式。
  • 行动建议:优先使用智能指针,避免原始指针,理解各种智能指针的适用场景和潜在问题。

5. 右值引用、移动语义、完美转发:掌握现代C++的性能优化手段

  • 详细解释:
    • std::move:无条件转换为右值,但不移动任何东西,用于表示对象适合移动操作。
    • std::forward:有条件转换为右值,仅当实参为右值时才转换,用于完美转发。
    • 完美转发:尽可能保持实参的类型和值类别,但存在花括号初始化、空指针、static const成员、重载函数名和位域等失败情况。
    • 引用折叠:模板实例化、auto类型推导、typedef和decltype等情况下的引用组合规则。
  • 举例:使用std::move实现移动构造函数,使用std::forward实现通用函数转发。
  • 行动建议:理解std::move和std::forward的含义,避免过度使用std::move,熟悉完美转发的限制,并掌握解决方法。

6. Lambda表达式:使用Lambda表达式,避免默认捕获模式

  • 详细解释:
    • Lambda表达式简化函数对象的创建,提高代码可读性。
    • 避免默认捕获模式:默认按引用捕获可能导致悬空引用,默认按值捕获不能解决悬空指针问题,且可能隐藏this指针的捕获。
    • 初始化捕获:C++14支持,可以将对象移动到闭包中,C++11中可通过手写类或std::bind模拟。
    • 泛型Lambda表达式:C++14支持,允许在Lambda表达式的形参中使用auto关键字。
  • 举例:使用Lambda表达式创建过滤器,使用初始化捕获将std::unique_ptr移动到闭包中。
  • 行动建议:避免使用默认捕获模式,使用初始化捕获移动对象到闭包中,对auto&&形参使用decltype以std::forward它们,并考虑Lambda表达式而非std::bind。

7. 并发API:使用基于任务的编程,注意线程安全

  • 详细解释:
    • 基于任务的编程:通过std::async实现,代码量少,可获取返回值和异常,避免手动线程管理。
    • 基于线程的编程:通过std::thread实现,需要手动管理线程,容易出现资源耗尽、资源超额和负载均衡等问题。
    • std::atomic:用于并发访问,保证操作的原子性,但对操作顺序没有足够限制,volatile用于访问特殊内存,对并发编程没有用。
  • 举例:使用std::async执行异步任务,使用std::atomic实现线程安全的计数器。
  • 行动建议:优先考虑基于任务的编程,如有异步的必要请指定std::launch::async,关注不同线程句柄析构行为,并对于并发使用std::atomic。

8. 其他微调:传值与置入,constexpr与const

  • 传值与移动:对于移动成本低且总是被拷贝的可拷贝形参,考虑按值传递。
  • 就地创建而非插入:考虑使用置入(emplace)代替插入。
  • 尽可能地使用constexpr:constexpr对象是const,它被在编译期可知的值初始化。
  • 让const成员函数线程安全:确保const成员函数线程安全,除非你确定它们永远不会在并发上下文(concurrent context)中使用。
  • 对于那些可移动总是被拷贝的形参使用传值方式

问答

Q: 什么是通用引用?如何区分通用引用和右值引用?

A: 通用引用是一种可以绑定到左值或右值的引用类型,形式为T&&,且T需要被推导。右值引用只能绑定到右值,形式为T&&,且T是已知的类型。区分的关键在于是否存在类型推导。

Q: 什么是完美转发?它有哪些失败的情况?

A: 完美转发是指将一个函数的形参传递给另一个函数,且目标函数接收到的实参与被传递给转发函数的实参保持一致,包括类型、值类别和const/volatile属性。失败的情况包括:花括号初始化器、空指针(0或NULL)、仅有声明的整型static const数据成员、重载函数的名字和位域。

Q: 如何解决通用引用和重载的冲突?

A: 解决办法包括放弃重载、传递const T&、传值、使用tag dispatch和使用std::enable_if约束模板。tag dispatch通过引入额外的标签参数来区分重载函数,std::enable_if通过type traits在编译期控制模板是否启用。

思维导图

目标读者

本书的目标读者是有一定C++基础,希望深入了解C++11和C++14新特性的程序员。本书假设读者已经熟悉C++的基本概念,如类、继承、多态等,并有一定的C++编程经验。本书适合以下人群阅读:

  1. 希望将C++98/03代码迁移到C++11/14的程序员。
  2. 希望深入了解C++11/14新特性的程序员。
  3. 希望编写更高效、更可靠的C++代码的程序员。
  4. 希望了解C++最佳实践的程序员。

本书不适合C++初学者阅读,因为本书涉及许多高级主题,需要读者有一定的C++基础才能理解。对于C++初学者,建议先学习C++的基本概念,再阅读本书。

作者背景

Scott Meyers是一位在C++社区享有盛誉的专家,拥有丰富的C++编程经验和深厚的理论知识。他撰写了多本畅销的C++书籍,包括“Effective C++”系列,这些书籍被广泛认为是C++程序员的必备读物。Meyers的教育背景包括计算机科学的博士学位,这为他深入理解C++的底层机制和高级特性奠定了坚实的基础。他的职业生涯主要集中在C++教育、咨询和写作方面,致力于帮助开发者编写更高效、更可靠的C++代码。Meyers的贡献不仅在于他清晰的写作风格和深入的技术分析,还在于他能够将复杂的C++概念转化为易于理解和实践的指导原则。他的工作对C++社区产生了深远的影响,许多C++程序员都从他的书籍和演讲中受益匪浅。

历史背景

在C++98/03时代,C++程序员面临着许多挑战,例如手动内存管理、复杂的类型声明、缺乏并发支持等。C++11的出现旨在解决这些问题,引入了许多新特性,如auto类型推导、智能指针、移动语义、Lambda表达式、并发API等,使得C++编程更加现代化、高效和安全。C++14则进一步完善了C++11的特性,例如泛型Lambda、返回类型推导等,使得C++编程更加简洁和灵活。本书《Effective Modern C++》正是在这样的历史背景下诞生的,旨在帮助C++程序员掌握如何高效地使用这些新特性,从而编写出更现代、更高效的C++代码。

章节摘要

音频

Comming Soon...