Effective Modern C++
一份宏大的作品,能够带动一个领域的蓬勃发展。C++ 社群在相对沉寂数十年之后迎来新一波浪潮,原因是,作为一门编程工具的最核心,C++ 的语言和标准库都出现巨大的拓展和强化。这一番大变革始自 2011,并分别于 2014、2017 持续进化。业界习惯性地将这些新版本统称为 Modern C++,用以区别“传统” C++。
作为知名书系的最新作品,《Effective Modern C++》的佳质和佳评一如其早期同门作品《Effective C++》和《More Effective C++》。本书延续作者 Scott Meyers 的一贯风格和质量,其最大特质就是,不但告诉我们 How,更用巨大而精良的篇幅告诉我们 Why。作者穷追猛打讲究再三的劲儿,常让我筋疲力尽,痛并快乐地爬行于某个条款之际拍案而叹:“天哪,还有下一页!”
而我,是一个在 C++ 领域已经生活了 25 年的老兵。
是的,我是一个在 C++ 领域已经生活了 25 年的老兵,这意味着我具备相当的 C++ 能力。尽管如此,面对号称全新语言的 Modern C++,我时或也有力不能逮、掩卷长叹的焦躁,特别是面对 Rvalue Reference(右值引用)、Perfect Forwarding(完美转发)、Metaprogramming(元编程)、Type Deduction(类型推导)、Type Traits(型别特征)等艰涩主题的时候。然而正是在特别艰涩的主题上你可以领受本书的巨大价值:如果你想完善根基,本书是你唯一的选择。
———— 侯捷推荐序
第 1 章 型别推导
C++98 仅有一套型别推导规则,用于函数模板。C++11 对这套规则进行了一些改动,并且增加了两套规则,一套用于 auto,另一套用于 decltype。后来,C++14 又扩展了能够运用 auto 和 decltype 的语境。型别推导应用范围的不断普及,使得人们不必再去写下那些不言自明或是完全冗余的型别。它还让 C++ 软件获得更高的适应性,因为在源代码的一个地方对一个型别实施的改动,可以自动通过型别推导传播到其他地方。然而,它也有可能导致写出来的代码较难看懂,因为编译器推导出的型别,可能不像我们所认为的那样显而易见。
想要使用现代 C++ 高效编程,就离不开对于型别推导操作的坚实理解。型别推导涉及的语境实在不胜枚举:在函数模板的调用中,在 auto 现身的大多数场景中,在 decltype 表达式中,特别是在 C++14 中那个神秘莫测的 decltype(auto) 结构中。
本章讨论的是每个 C++ 开发工程师都需要了解的有关型别推导的知识。本章解释了模板型别推导如何运作,auto 的型别推导如何构建在此运作规则之上,以及 decltype 独特的型别推导规则。本章还教你如何迫使编译器来展示其型别推导的结果,从而让你确信该结果如你所愿。
条款 1 理解模板型别推导
1 | template <typename T> |
在编译期,编译器会通过 expr 推导两个型别:一个是 T 的型别,另一个是 ParamType 的型别,这两个型别往往不一样。因为,ParamType 常会包含了一些饰词,如 const 或引用符号等限定词。
我们很自然地会认为,T 的型别推导结果和传递给函数的实参型别是同一的。但是,这一点并不总是成立。T 的型别推导结果,不仅仅依赖 expr 的型别,还依赖 ParamType 的形式。具体要分三种情况讨论:
情况 1:ParamType 是个指针或引用,但不是个万能指针
1 | template <typename T> |
cx 和 rx 的示例中,T 被推导为 const int,说明对象的常量性会成为 T 的型别推导结果的组成部分。
rx 具有引用型别,但 T 并未被推导成一个引用。原因在于,rx 的引用性会在型别推导过程中被忽略。
对于 param 型别改成 const T&,由千我们现在会假定 param 具有 const 引用型别,T 的型别推导结果中包含 const 也就没有必要了。
如果 param 是个指针(或指涉到 const 对象的指针)而非引用, 运作方式本质上并无不同。
情况 2:ParamType 是个万能引用
1 | template <typename T> |
- 如果
expr是个左值,T和ParamType都会被推到为左值引用。这个结果具有双重的奇特之处:首先,这是在模板型别推导中,T被推导为引用型别的唯一情形。其次,尽管在声明时使用的是右值引用语法,它的型别推导结果却是左值引用。 - 如果
expr是个右值,则应用“常规”(即情况 1 中的)规则。
条款 24 详尽解释了为何上述例子会产生这样的结果。关键之处在于,万能引用形参的型别推导规则不同于左值引用和右值引用形参。具体地,当遇到万能引用时,型别推导规则会区分实参是左值还是右值。而非万能引用是从来不会作这样的区分的。
情况 3:ParamType 既非指针也非引用
数组实参
函数实参
要点速记
- 在模板型别推导过程中,具有引用型别的实参会被当成非引用型别来处理。换言之,其引用性会被忽略。
- 对万能引用形参进行推导时,左值实参会进行特殊处理。
- 对按值传递的形参进行推导时,若实参型别中带有
const或volatile饰词,则它们还是会被当作不带const或volatile饰词的型别来处理。 - 在模板型别推导过程中,数组或函数型别的实参会退化成对应的指针,除非它们被用来初始化引用。










