从 2016 年 8 月份开始读这本书,限于目前大陆这边还没有中文版,所以是一边读一边翻译,但是自己的英语水平很一般,所以并没有以翻译的角度来写文章,怕自己的水平糟蹋了这本好书,所以基本上就是读懂了书中的意思,然后按照自己的理解写出来,截止 2017 年 6 月 5 号已经全部完成,目前是第一版,自己还在不断调整语句、格式和内容。力求不误导人。也希望广大的 C++爱好者可以给我提出一些修改建议。
Effective Modern C++ 目录
Item1 Understand template type deduction
Tips:
- 在模版类型推到的时候,如果传递的参数是引用类型,那么可以看作是非引用类型的,也就是说类型的引用部分被忽略。
- 当对通用引用类型的参数进行类型推导时,左值参数需要特殊对待。
- 当推导正常的参数类型时,const和volatile类型的参数会被忽略掉const和volatile部分。
- 在模版类型推导时,如果参数是数组或是函数名会退化为指针,除非这些参数是用来初始化 引用的。
Item2 Understand auto type deduction
Tips:
- auto类型推导通常和模版类型推导是一致的,但是auto类型推导对于{}会推导为std::initializer_list,但是模版类型无法对其进行推导
- auto对于函数返回值的类型推导和lambda参数类型推导时就是隐式的模版类型推导,并不是auto类型推导,对于{}无法进行推导
Tips:
- decltype推导出来的类型几乎总是和目标类型是一致的
- 对于类型为T的左值表达式来说,decltype总是推导成T&
- C++14支持decltype(auto)声明变量和auto类似,可以对初始值进行类型推导,但是遵循的是decltype的规则
Item4 Know how to view deduced types
Tips:
- 类型推导可以借助于IDE,或者是编译器的错误输出以及Boost TypeIndex库
- 一些工具的类型推导结果可能是对我们所有帮助的,但是不一定是准确的,因此理解C++的类型推导规则仍然是必不可少的
Item5 Prefer auto to explicit type declarations
Tips
- auto变量必须初始化,可以缓解因为类型不匹配导致的可移植性或效率问题,可以缓解在重构过程中需要显示输入很长的变量类型名称。
- auto类型的变量受制于Item2和Item6中提到的陷阱
Item6 Use the explicitly typed initializer idiom when auto deduces undesied types
Tips
- 对于一些看不见的proxy类型,使用auto对这类初始化表达式进行类型推导会推导出错误的类型
- 通过显示的类型初始化惯用法可以强制auto推导出目标类型
Item7 Distinguish between () and {} when creating objects
Tips
- {}初始化是最广泛的初始化语法,它可以阻止窄化转换,并且避免了C++最复杂的语法解析
- 在构造函数做函数重载的时候,{}会优先匹配带有
std::initializer_list
参数的版本,即使其他构造函数看起来更匹配- 对与std::vector两个参数的构造函数来说,其{}和()两种初始化方式有很大的不同
- 在模版中对于{}和()初始化如何进行选择是一个挑战
Item8 Prefer nullptr to 0 and NULL
Tips
- 优先使用nullptr替换0和NULL
- 避免同时重载带有整型参数和指针类型的参数
Item9 Prefer alias declarations to typedefs
Tips
- typedef 不支持模版化,但是using的别名声明可以
- 模版别名避免了传统的typedef带来的
::type
后缀,以及在类型引用的时候需要的typename前缀- C++14给所有的C++11模版类型萃取提供了别名
Item10 Prefer scoped enums to unscoped enums
Tips
- C++98种的枚举众所周知是无作用域限制的
- C++11中的枚举类是有作用域限制的,不能进行隐式的类型转换需要使用C++的类型cast进行转换
- 无论是枚举类还是传统的枚举类型都支持指定底层的存储,对于枚举类来说默认的底层存储类型是int,而传统的枚举类型其底层存储是未知的,需要在编译器进行选择
- 枚举类总是可以进行前向声明的,而枚举类型则不行,必须是在明确指定其底层存储的时候才能进行前向声明
Item11 Prefer deleted functions to private undefined ones
Tips
- 优先使用delete来删除函数替换放在私有作用域中未定义的
- 任何函数都可以被删除,包括非成员函数,模版实例化等
Item12 Declare overriding function override
Tips
- 对于要重写的函数添加override关键字,让编译器负责检查
- 成员函数的引用标识符可以识别出(*this)的不同,是左值类型,还是右值类型
Item13 Prefer const_iterators to iterators
Tips
- 优先使用const_iterator替换iterator
- 为了是代码更通用,应该优先使用非成员函数版本的begin、end、cbegin、cend等
Item14 Declare functions noexcept if they won’t emit exception
Tips
- noexcept 是函数接口的一部分,这意味着调用者会依赖它
- 使用noexcept声明的函数相比于没有使用noexcept声明的函数代码更具可优化性
- noexpect对于move、swap、内存分配函数、析构函数等具有特别的价值
- 大多数函数都是异常中立的而不是noexcept
Item15 Use constexpr whenever possible
Tips
- constexpr对象是const,它的初始值是编译期的
- constexpr函数当传入的参数是编译期值时可以产生编译期的结果
- constexpr对象和函数可以广泛使用在非constexpr修饰的对象和函数上下文中
- constexpr关键字是对象以及函数接口的一部分
Item16 Make const member functions thread safe
Tips
- 为了让const成员函数是线程安全的,除非你确定不会在并发环境中调用
- 使用std::atomic变量可能会提供比mutex更好的性能,但是它仅仅适合操作单个变量和内存位置的操作
Item17 Understand special member function generation
Tips
- 编译器可能会生成的特殊成员函数会有默认构造函数、析构函数、拷贝操作、移动操作等
- 仅仅当类没有显式的声明移动操作、拷贝操作和析构函数的时候,编译器才会生成默认的移动构造函数
- 仅仅当类没有显式的声明拷贝构造函数、或是声明了移动构造函数时编译器才会生成默认的拷贝构造函数,和拷贝构造函数类似,仅仅当类没有显式的声明拷贝赋值操作符或是移动构造操作符时编译器才会生成默认的拷贝赋值操作符,不建议在具有明确声明的析构函数的类中生成复制操作。
- 成员函数模版不会阻止编译器生成特殊的成员函数
Item18 Use std::unique_ptr for exclusive-ownership resource management
Tips
- std::unique_ptr很小、很快、是一个只能移动的,独占管理资源的智能指针。
- 默认情况看下资源的删除使用的是delete,但是可以定制删除器,有状态的删除器会导致std::unique_ptr对象的大小增长。
- 将std::unique_ptr转换为std::shared_ptr是很容易的
Item19 Use std::shared_ptr for shared-ownership resource management
Tips
- std::shared_ptr提供了方便的方法进行垃圾回收可以对任意共享资源的生命周期进行管理。
- 相比于std::unique_ptr,std::shared_ptr对象通常情况下要大两倍,这是因为它要分配一个控制块,该控制块中包含了一个原子类型的引用计数、删除器、弱引用计数等
- 默认的资源释放是通过delete,但是std::shared_ptr支持自定义删除器,并且删除器的类型不影响std::share_ptr的类型和大小。
- 避免从一个指针类型的变量创建std::shared_ptr
Item20 Use std::weak_ptr for std::shared_ptr like pointers that can dangle
Tips
- std::weak_ptr可以探查std::shared_ptr指向的指针是否是悬挂指针
- 使用std::weak_ptr的一些潜在的场景包括,对象缓存、观察者模式、阻止std::shared_ptr的循环引用
Item21 Perfer std::make_unique and std::make_shared to direct use of new
Tips
- 相比于直接使用new,make系列的函数消除了源代码重复、提升了异常安全性,并且std::make_shared和std::allocate_shared生成的代码更小更快
- 对于希望自定义删除器以及通过{}进行初始值的设定时,不适合使用make系列函数
- 对于std::shared_ptr来说有两类场景不适合使用make系列函数,第一个就是需要自定义管理内存的,第二个就是管理大对象时,并且存在std::weak_ptr比std::shared_ptr生命周期更长的情况
Item22 When using the Pimpl, define specific member functions in the implementation file
Tips
- Pimpl的惯用法通过减少类的使用者对类实现的编译依赖从而缩小编译所花费的时间
- pImpl使用std::unique_ptr时,类的特殊函数声明放在头文件中,具体实现放在实现文件中的,避免了头文件中包含了std::unique_ptr析构相关的函数,导致编译错误
- 上面的建议适用于std::unique_ptr,但是不适用于std::shared_ptr
Item23 Understand std::move and std::forward
Tips
- std::move的实现其实是一个无条件将类型cast转换为右值类型,就本身而言没有移动任何东西
- std::forward就是根据参数的类型有条件的进行cast转换,如果输入的参数是一个右值就转换为右值,否则就转换为左值
- 无论是std::move还是std::forward在运行时都没有做任何事情
Item24 Distinguish universal references from rvalues reference
Tips
- 如果一个模版函数的参数类型是T&&,并且T的类型是推导出来的,或者一个对象使用auto&&这样的声明,那么这个函数的参数和要声明的对象都是通用引用类型
- 如果类型的声明不是type&&,或者没有发生类型推导,那么type&&表示的是一个右值引用
- 如果通用引用初始化的时候传入的是一个右值,那么就和右值引用一样,如果传入的是左值那么就喝左值引用是一样的
Item25 Use std::move on rvalue reference, std::forward on universal references
Tips
- 应用std::move到右值引用,std::forward到通用引用
- 对于右值引用和通用引用作为值从函数返回时本质上都是做了相同的事情
- 如果希望通过编译器进行返回值优化,则不要将std::move或std::forward应用到局部对象
Item26 Avoid overloading on universal references
Tips
- 在通用引用上进行重载,几乎总是会导致通用引用重载的版本比预期被调用的次数更频繁。
- 完美转发构造函数是非常有问题的,因为它们通常比非const左值的拷贝构造函数更好地匹配,并且可以劫持派生类对基类拷贝构造和移动构造函数的调用。
Item27 Familiarize yourself with alternatives to overloading on universal references
Item28 Understand reference collapsing
Item29 Assume that move operations are not present,not cheap,and not used
Item30 Familiarize yourself with perfect forwarding failure cases
Item31 Avoid default capture modes
Item32 Use Init capture to move objects into closures
Item33 Use decltype on auto&& parameters to std::forward them
Item34 Prefer lambdas to std::bind
Item35 Prefer task-based programming to thread- based
Item36 Specify std::launch::async if asynchronicity is essential
Item37 Make std::threads unjoinable on all paths
Item38 Be aware of varying thread handle destructor behavior
Item39 Consider void futures for one-shot event communication
Item40 Use std::atomic for concurrency, volatile for specific memory
Item41 Consider pass by value for copyable parameters that are cheap to move and always copied.
Item42 Consider emplacement instead of insertion