博客 阅读时间 6 分钟

现代化您的 C++ 代码库:C++ 禁用特性指南

分享本文
Modernize C++ Codebase: Banned C++ Features Guide

重构被定义为这样一种过程:以不改变代码外部行为、同时改善其内部结构的方式,修改一个软件系统。

我们可以归纳出管理者对代码重构通常采取的三种做法:

  • 迭代式重构:即使团队拥有最优秀的架构师、设计师和开发者,您的应用程序也不可能在第一个迭代中就做到完美。一种既不投入大量资金、也不浪费时间的务实重构方式,是把重构融入开发流程,在每个迭代之后进行。
  • 必要时才重构:应用程序部署之后,会收到一些缺陷报告;如果修复它们很费时间,或者某些客户需求很难开发并集成到现有系统中,重构就可以成为提升代码库质量的好办法。但在这种情况下风险可能很大,必须小心避免在现有代码中引入回归。
  • 从不重构:有时,即使现有应用程序存在许多问题,也从不进行重构,因为管理层不愿在这个过程上投入;支持团队不得不应付所有缺陷和反馈带来的压力。

如果您的团队定期重构 C++ 项目,并选择了使用一些新的 C++11/C++14 特性,那么看看其他知名 C++ 项目是如何重构代码以采用新标准的,会很有参考价值。

Chrome 浏览器就是一个很好的研究对象:他们定期重构代码库;新标准一经批准并获得编译器支持,Chrome 开发团队就选择向这些新标准迈进。

Chrome 浏览器是一个成熟的 C++ 项目,探究它的代码是如何实现和设计的非常有意思。事实上,Chrome 拥有数百万用户,这要求其开发团队产出高效的代码。

如果您决定重构自己的 C++ 代码库以采用新标准的特性,值得看一看这份有趣的Google 文档,它列出了允许使用的 C++11/C++14 特性,也列出了被禁用的特性。

这份文档中最有价值的部分是被禁用特性的清单及其禁用原因。仔细研究这份清单、看看它是否也适用于您的项目,是非常值得的。

下面是文档中被禁用的 C++11/C++14 特性:

C++11 被禁用的特性

特性或库 代码片段 说明 文档链接 备注与讨论帖
内联命名空间(Inline Namespaces) inline namespace foo { ... } 让命名空间的版本管理更方便 内联命名空间 Google 风格指南中被禁用。尚不清楚它将如何与组件协同工作。
long long类型 long long var = value; 至少 64 位的整数 基本类型 如果需要 64 位数字,请使用 stdint.h 类型。讨论帖
引用限定的成员函数(Ref-qualified Member Functions) class T { void f() & {} void f() && {} }; t.f();  // first T().f();  // second std::move(t).f();  // second 允许类成员函数仅绑定到作为右值或左值的 |this|。 const、volatile 和引用限定的成员函数 Google 风格指南中被禁用。只有在获得styleguide/c++/OWNERS讨论帖
用户定义字面量(User-Defined Literals) 类型 var = literal_value_类型 允许用户自定义字面量表达式 用户定义字面量 Google 风格指南
thread_local 存储类 thread_local int foo = 1; 将变量放入线程局部存储。 存储期 在 Mac 上有一些出人意料的影响(讨论fork)。请使用SequenceLocalStorageSlot以获得序列(sequence)支持,其他情况使用ThreadLocal/ThreadLocalStorage

C++14 被禁用的特性

特性 代码片段 说明 文档链接 备注与讨论帖
函数返回类型推导 auto f() { return 42; } decltype(auto) g() { return 42; } 允许根据模板或decltype规则,从函数的 return 语句自动推导其返回类型。 返回类型推导 暂时禁用,因为它可能导致 clang 陷入无限循环。我们预计在该缺陷修复后会允许使用。它的使用场景应该很少,主要用于抽象模板代码。讨论帖
泛型 lambda [](const auto& x) { ... } 允许使用auto(按照适用于模板的规则)推导 lambda 参数类型。 lambda 表达式 暂时禁用,因为它可能导致 clang 陷入无限循环。我们预计在该缺陷修复后会允许使用。讨论帖

C++11 被禁用的库特性

特性 代码片段 说明 文档链接 备注与讨论帖
对齐存储(Aligned storage) std::aligned_storage<10, 128> 为需要特定对齐的对象提供未初始化的存储。 std::aligned_storage MSVC 2017 的实现在大于 sizeof(double)= 8 字节的边界上不做对齐。请改用alignas(128) char foo[10];发现此问题的补丁
绑定操作(Bind Operations) std::bind(functionargs, ...) 声明一个绑定了特定参数的函数对象 std::bind 请改用base::Bind。与std::bindbase::Bind通过禁止绑定捕获型 lambda、并强制调用者把裸指针声明为Unretained讨论帖
C 浮点环境 <cfenv><fenv.h> 为兼容 C 的代码提供浮点状态标志和控制模式 标准库头文件 <cfenv> 因担心它会助长更重度依赖模板的接口风格,被Google 风格指南禁用。
日期和时间工具 <chrono> 标准日期和时间库 日期和时间工具 Time中的base/中的许多类重叠。暂时继续使用base/类。
异常 <exception> 对异常抛出和处理的增强 标准库头文件 <exception> 异常被Google 风格指南禁用,并在 Chromium 编译中被关闭。请注意,前面已明确允许使用noexcept说明符。讨论帖
函数对象(Function Objects) std::function 封装一个标准的多态函数 std::function 请改用base::Callback。与std::functionbase::Callback直接支持 Chromium 的引用计数类和弱指针,并处理额外的线程安全问题。讨论帖
比率模板类(Ratio Template Class) std::ratio<分子分母> 提供编译期有理数 std::ratio 因担心它会助长更重度依赖模板的接口风格,被Google 风格指南禁用。
正则表达式 <regex> 标准正则表达式库 正则表达式库 与 Chromium 中的许多正则表达式库重叠。拿不准时,请使用 re2。
共享指针(Shared Pointers) std::shared_ptr 通过引用计数实现指针的共享所有权 std::shared_ptr 对 Chromium 而言还需要更多评估,目前对采用这一特性的推动力也不足。Google 风格指南讨论帖
线程库(Thread Library) <thread>及相关头文件,包括<future><mutex><condition_variable> 提供使用std::thread及其相关设施的标准多线程库 线程支持库 base/中的许多类重叠。暂时继续使用base/类。base::ThreadMessageLoop紧密耦合,这将使其难以被替换。我们应该研究使用标准的互斥量(mutex)或 unique_lock 等,来替换我们的锁/同步类。

C++14 被禁用的库特性

本节列出 Chromium 代码库中不允许使用的 C++14 库特性。

特性 代码片段 说明 文档链接 备注与讨论帖
std::chrono字面量 using namespace std::chrono_literals; auto timeout = 30s; std::chrono类型更容易构造。 std::literals::chrono_literals::operator""s <chrono>被禁用而禁用。

结语

研究知名开源项目的源代码、理解它们的设计与实现选择,也许是学习如何编写高效代码的最佳方式之一。知名的 C++ 项目通常由努力践行最佳实践的 C++ 专家开发。

总而言之,看一看 Chromium 的源代码吧——它绝对值得您投入时间。

分享本文