博客 阅读时间 6 分钟

从 Folly 源代码学习新的 C++11 特性

分享本文
Learn the new C++11 features from the Folly source code.

六年前,Facebook 发布了名为Folly的 C++ 库,这是一个大型的可复用 C++ 库组件集合,在 Facebook 内部被广泛使用。

但是,已经有许多成熟的开源 C++ 库存在,为什么还要再造一个呢?以下是他们的网站上解释的创建动机:

Folly(名称大致取自 Facebook 开源库的首字母缩写)是一个以实用性和效率为设计目标的 C++11 组件库。它是对 Boost 等库以及当然还有标准库的补充(而非竞争)。std事实上,只有当我们需要的东西不存在,或者无法满足所需的性能要求时,我们才会着手定义自己的组件。

例如,这里有一份详细解释,说明 Folly 为什么引入了另一个 vector 类 FBVector。正如 Folly 开发者所宣称的,它是一个 C++11 组件库——只要看一眼源代码就能充分证实这一点:C++11 特性被广泛使用,而且几乎所有新的 C++11 特性都用到了。

几年前 C++0x 公布时,我以为它不会对 C++ 语言产生太大影响,但我错了。看看这段来自 Folly 的代码片段——它看起来就像是用一门新语言开发的。

c0

同样的观察几乎适用于 Folly 的全部源代码;它的实现看起来与 C++03 源代码截然不同。

对于想要掌握新 C++11 特性的开发者来说,最好的方法之一就是看看成熟的库是如何使用它们的。Folly 是探索这些新特性的绝佳选择。让我们在它的源代码中探索其中一些特性。

1. auto

C++11 通过 auto 关键字引入了类型推断,这意味着编译器在声明处推断变量的类型。Folly 几乎所有的变量声明都使用 auto;下面是它源代码中的一个例子。

c1

使用 auto 关键字,您可以少花时间显式写出编译器本来就知道的类型。

2. nullptr

常量 0 传统上承担着整数常量和空指针常量的双重角色。C++11 通过引入一个专门表示空指针常量的新关键字 nullptr 纠正了这一点。

在 Folly 源代码中,所有空指针都用新关键字 nullptr 表示;没有任何地方使用常量 0。

3. shared_ptr

智能指针并不是新概念;许多库多年前就实现了它们,其中最流行的是boost::shared_ptr。新的变化是它们被标准化了——不再需要借助外部库来使用智能指针。

Folly 大量使用标准化的 shared 指针;其源代码中只剩下少量原始指针。

4. 强类型枚举

C++ 中的"传统"枚举会把其枚举值导出到外围作用域,如果同一作用域中的两个不同枚举定义了同名的枚举值,就可能导致名称冲突。C++11 使用 enum class 语法引入了有作用域的枚举。它们不再把枚举值导出到外围作用域。此外,我们现在还可以指定枚举的底层类型。

c2

5. static_assert(静态断言)

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

c3

6. 可变参数模板

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

c4

7. 基于范围的 for 循环

C++11 扩展了 for 语句,支持基于范围的集合迭代。它让代码更简单、更整洁。Folly 大量使用这个特性;下面是 Folly 中的一个例子。

c6

8. 初始化列表

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

c7

下面是它的调用方式。

c8

9. noexcept

如果一个函数不会抛出异常,或者程序不打算处理该函数抛出的异常,那么可以将该函数声明为noexcept。

下面是 Folly 源代码中的一个例子。

c9

10. move(移动语义)

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

c12c10

这里有一份不错的文档,更好地解释了移动语义的好处。

11. lambda(匿名函数)

C++11 提供了创建匿名函数的能力,称为 lambda 函数。您可以参考这里了解关于这一新特性的更多细节。

Folly 在许多函数中使用了它们;下面是它源代码中的一个例子:

c14

12. 显式默认和删除的特殊成员函数

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

c15

13. override 标识符

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

Folly 大量使用这个新特性:

c18

14. std::thread

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

c20

15. 无序容器

无序容器是一种哈希表。C++11 提供了四种标准无序容器:

  • unordered_map
  • unordered_set
  • unordered_multimap
  • unordered_multiset

Folly 在许多地方使用了这些新容器。

c21

结论

Folly 几乎使用了所有新的 C++11 特性。研究它的源代码将帮助您更好地理解 C++ 的新能力,以及它们在实践中是如何使用的。

我鼓励每一位对 C++11 感兴趣的 C++ 开发者下载 Folly 的源代码,领略现代 C++ 的威力。

分享本文