过去几年,我们一直在谈论"C++ 复兴"。必须承认,微软是这场运动的重要推手;我记得这个视频,Craig Symonds 和 Mohsen Agsen 在其中谈到了这一点。
2011 年,微软在许多文章中宣布 C++ 的回归,Herb Sutter 等微软 C++ 专家发表了大量演讲,解释 C++ 为何归来,主要推荐大家使用现代 C++。与此同时,C++11 标准获得批准,我们开始把 C++ 当作一门新语言来谈论。
到 2011 年,C++ 已经使用了 30 多年。要让开发者相信新 C++ 确实简化了 C++ 使用中许多令人沮丧的方面、并且有了一种改进 C++ 代码的全新现代方式,并非易事。
以内存管理为例——这也许是 C++ 最受批评的方面之一。多年来,对象分配都使用 new 关键字完成,开发者必须记得在代码的某处调用 delete。"现代 C++"通过推广智能指针的使用解决了这个问题。
几年前 C++0x 公布时,我以为它不会对 C++ 语言产生太大影响,但我错了。看看这段来自 Folly 的代码片段——它看起来就像是用一门新语言开发的。

同样的观察几乎适用于 Folly 的全部源代码;它的实现看起来与 C++03 代码截然不同。
如何限制 C++ 历史遗留的影响?
没有什么神奇的解决方案。我们可以寄望于 C++ 编译器通过对过时做法发出警告来帮助我们,比如那些老旧的字符串操作函数(strcpy、strcat 等)。但这种解决方案影响不大。现代 C++ 很大程度上在于学习和实践新的惯用法。
另一种解决方案是,把一门名为"Modern C++(现代 C++)"的新语言视为已经诞生。让我们做和之前一样的网络搜索,但这次搜索"Modern C++"对象分配——第一个链接讲的就是智能指针。
探索新语言 MC++
领略 MC++ 威力的更好方法,是研究一个使用其特性的成熟项目的源代码。Facebook 的 Folly 就是一个非常好的选择。
让我们来看看 Folly 中使用的一些 MC++ 特性:
1. auto
C++11 通过 auto 关键字引入了类型推断,这意味着编译器在声明处推断变量的类型。Folly 几乎所有的变量声明都使用 auto;下面是它源代码中的一个例子。

使用 auto 关键字,您可以少花时间写出编译器本来就知道的东西。
2. nullptr
常量 0 传统上承担着整数常量和空指针常量的双重角色。C++11 通过引入一个专门表示空指针常量的新关键字 nullptr 纠正了这一点。
在 Folly 源代码中,所有空指针都用新关键字 nullptr 表示;没有任何地方使用常量 0。
3. shared_ptr
智能指针并不是新概念;许多库多年前就实现了它们,其中最流行的是boost::shared_ptr。新的变化是它们被标准化了——不再需要借助外部库来使用智能指针。
Folly 大量使用标准化的 shared 指针;其源代码中只剩下少量原始指针。
4. 强类型枚举
C++ 中的"传统"枚举会把其枚举值导出到外围作用域,如果同一作用域中的两个不同枚举定义了同名的枚举值,就可能导致名称冲突。
C++11 引入了 enum class 关键字。它们不再把枚举值导出到外围作用域。此外,我们现在还可以为枚举指定底层类型。

5. static_assert(静态断言)
C++11 引入了一种在编译期测试断言的新方式,使用新关键字 static_assert。这个特性对于给模板参数添加条件非常有用,如 Folly 源代码中的这个模板类所示:

6. 可变参数模板
可变参数模板是可以接受任意数量、任意类型模板参数的模板。类模板和函数模板都可以是可变参数的。Folly 定义了许多可变参数模板;下面是 Folly 源代码中的两个可变参数模板函数:

7. 基于范围的 for 循环
C++11 增强了 for 语句,以支持遍历集合的"foreach"范式。它让代码更简单、更整洁。Folly 大量使用这个特性;下面是一个例子:

8. 初始化列表
在 C++03 中,初始化列表只适用于数组;在 C++11 中,它们不再局限于数组。接受{}列表的机制是一个接受std::initializer_list<T>类型参数的函数(通常是构造函数)。下面是一个接受 std::initializer_list 作为参数的函数的例子。

下面是它的调用方式。

9. noexcept
如果一个函数不会抛出异常,或者程序不打算处理该函数抛出的异常,那么可以将该函数声明为noexcept。
下面是 Folly 源代码中的一个例子。

10. move(移动语义)
C++11 引入了右值引用(用 && 表示)的概念,以区分左值引用和右值引用。左值是有名字的对象,而右值是没有名字的对象(临时对象)。移动语义允许修改右值。
为了支持这一点,C++11 引入了两个新的特殊成员函数:移动构造函数和移动赋值运算符。


这里有一份不错的文档,更好地解释了移动语义的好处。
11. lambda(匿名函数)
C++11 提供了创建匿名函数的能力,称为 lambda 函数。您可以参考这里了解关于这一新特性的更多细节。
Folly 在许多函数中使用了它们;下面是它源代码中的一个例子:

12. 显式默认和删除的特殊成员函数
在 C++03 中,对于没有自行定义的类,编译器会提供默认构造函数、拷贝构造函数、拷贝赋值运算符(operator=)和析构函数。程序员可以通过定义自定义版本来覆盖这些默认实现。
然而,对这些默认实现的创建几乎无法控制。例如,要让一个类本质上不可拷贝,需要声明一个私有的拷贝构造函数和拷贝赋值运算符而不定义它们。
在 C++11 中,某些特殊成员函数可以被显式禁用。例如,下面的类型是不可拷贝的,这让代码更简单、更整洁。

13. override 标识符
在 C++03 中,本想重写基类函数,却可能意外创建一个新的虚函数。
override特殊标识符意味着编译器会检查基类中是否存在与此签名完全相同的虚函数。如果不存在,编译器将报告错误。
Folly 大量使用这个新特性:

14. std::thread
提供了一个线程类(std::thread),它接受一个函数对象——以及可选的一系列要传递给它的参数——在新线程中运行。
在 C++11 中,使用线程更加简单;下面是定义新线程的新标准方式,取自 Folly 源代码:

15. 无序容器
无序容器是一种哈希表。C++11 提供了四种标准无序容器:
- unordered_map
- unordered_set
- unordered_multimap
- unordered_multiset
Folly 在许多地方使用了这些新容器。

结论:
给 C++ 新手开发者的一条建议:把 C++ 想象成已经改了名字,在网络搜索时使用"Modern C++"而不是简单的 C++。搜索结果会大不相同——只搜 C++ 往往会让您找到过时的做法。
