现代 C++
使用 C++20、C++23 和 C++26 惯用法编写现代 C++ 的指南。重点在于消除漏洞类别并减少样板代码的模式,并特别强调来自 Trail of Bits 的安全性。
何时使用此技能
- 编写新的 C++ 函数、类或库
- 现代化现有 C++ 代码(C++20 之前的模式)
- 在传统方法与现代方法之间进行选择
- 处理安全关键型或安全性敏感的 C++ 代码
- 审查 C++ 代码的现代惯用法采纳情况
何时不使用此技能
- 用户明确要求使用较旧的标准:尊重约束(嵌入式、旧版 ABI)
- 纯 C 代码:此技能特定于 C++
- 构建系统问题:CMake、Meson、Bazel 配置不在范围内
- 非 C++ 项目:C++ 不是主要语言的混合代码库
应避免的反模式
| 避免 | 改用 | 原因 |
|---|---|---|
new/delete | std::make_unique、std::make_shared | 消除泄漏、双重释放 |
| 原始拥有指针 | std::unique_ptr、std::shared_ptr | RAII 拥有权语义 |
C 数组 (int arr[N]) | std::array<int, N> | 边界感知、值语义 |
| 指针 + 长度参数 | std::span<T> | 非拥有、可检查边界 |
printf / sprintf | std::format、std::print | 类型安全、无缓冲区溢出 |
C 风格强制转换 (int)x | static_cast<int>(x) | 明确意图、可审计 |
#define 常量 | constexpr 变量 | 有作用域、有类型、可调试 |
SFINAE / enable_if | 概念 + requires | 可读性强的约束和错误信息 |
| 错误码 + 输出参数 | std::expected<T, E> | 可组合、类型安全的错误 |
union | std::variant | 类型安全、无隐晦的 UB |
原始 mutex.lock()/unlock() | std::scoped_lock | 异常安全、无死锁 |
std::thread | std::jthread | 自动汇合、支持停止令牌 |
assert() 宏 | contract_assert (C++26) | 对工具可见、可配置 |
| 手动 CRTP | 推导 this (C++23) | 更简单、无模板样板代码 |
| 宏代码生成 | 反射 (C++26) | 零开销、可组合 |
请参见 anti-patterns.md 获取完整表格(30+ 种模式)。
决策树
你在做什么?
|
+-- 编写新 C++ 代码?
| +-- 默认使用现代惯用法(C++20/23)
| +-- 选择编译器支持的最新标准
| +-- 参见下面的功能层级
|
+-- 现代化现有代码?
| +-- 从第 1 层(C++20/23)替代方案开始
| +-- 按安全影响排序优先级(内存 > 类型 > 风格)
| +-- 参见 anti-patterns.md 获取迁移表
|
+-- 处理安全关键型代码?
| +-- 启用编译器加固标志(见下文)
| +-- 启用加固 libc++ 模式
| +-- 在 CI 中运行清理器
| +-- 参见 safe-idioms.md 和 compiler-hardening.md
|
+-- 使用 C++26 特性?
+-- 反射:是的,为此做计划(GCC 16+)
+-- 契约:谨慎使用,适用于新的 API 边界
+-- std::execution:等待生态系统成熟
+-- 参见 cpp26-features.md
功能层级
功能按当今实际可用性排序,而不是按标准版本。
第 1 层:今天使用(C++20/23,编译器支持良好)
| 特性 | 替代品 | 标准 |
|---|---|---|
概念 + requires | SFINAE、enable_if | C++20 |
| Ranges + views | 原始迭代器循环 | C++20 |
std::span<T> | 指针 + 长度 | C++20 |
std::format | sprintf、iostream 链 | C++20 |
三路比较 <=> | 手动比较运算符 | C++20 |
std::jthread | std::thread + 手动汇合 | C++20 |
| 指定初始化器 | 位置结构体初始化 | C++20 |
std::expected<T,E> | 错误码、边界处的异常 | C++23 |
std::print / std::println | printf、std::cout << | C++23 |
推导 this | CRTP、const/non-const 重复 | C++23 |
std::flat_map | 针对读取密集型使用的 std::map | C++23 |
一元 std::optional | 嵌套的 if 检查 optional | C++23 |
请参见 cpp20-features.md 和 cpp23-features.md。
第 2 层:立即部署(无需提升标准版本)
这些方法无需更改 C++ 标准版本即可提高安全性:
- 编译器加固标志 —
-D_FORTIFY_SOURCE=3、-fstack-protector-strong、-ftrivial-auto-var-init=zero - 加固 libc++ —
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST带来约 0.3% 开销的边界检查 - CI 中的清理器 — 至少使用 ASan + UBSan;并发代码使用 TSan
- 警告标志 —
-Wall -Wextra -Wpedantic -Werror
第 3 层:规划使用(C++26,值得围绕其重构)
反射是 C++26 中最具变革性的特性。它消除了:
- 序列化样板代码(一个通用函数替代每个结构体的
to_json) - 代码生成器(protobuf codegen、Qt MOC)
- 基于宏的注册和枚举到字符串的黑客手段
GCC 16(2026 年 4 月)已合并反射。规划新代码以从中受益。
第 4 层:关注(C++26,需要成熟)
- 契约(
pre/post/contract_assert)— 比assert()更好,但不支持虚函数,且编译器支持有限。谨慎采用,适用于新的 API 边界。 - std::execution(发送器/接收器)— 强大的异步框架,但学习曲线陡峭,没有附带调度器,且文档匮乏。等待生态系统成熟。
请参见 cpp26-features.md。
编译器加固快速参考
基本标志(GCC + Clang)
-Wall -Wextra -Wpedantic -Werror
-D_FORTIFY_SOURCE=3
-fstack-protector-strong
-fstack-clash-protection
-ftrivial-auto-var-init=zero
-fPIE -pie
-Wl,-z,relro,-z,now
Clang 特有
-Wunsafe-buffer-usage
加固 libc++(仅 Clang/libc++)
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
Google 在 Chrome 及其服务器集群中部署了此方案:约 0.3% 开销,发现 1000+ 个错误,生产环境段错误减少 30%。
请参见 compiler-hardening.md 获取完整指南。
应拒绝的合理化借口
| 合理化借口 | 为什么是错的 |
|---|---|
| “它能编译通过且没有警告” | 警告取决于你启用了哪些标志。添加 -Wall -Wextra -Wpedantic。 |
| “ASan 对于生产环境来说太慢了” | 使用 GWP-ASan 进行基于采样的生产环境检测(约 0% 开销)。 |
| “我们只使用安全容器” | 迭代器失效和未检查的 optional 访问仍然可以被利用。 |
| “智能指针更慢” | std::unique_ptr 相比裸指针具有零开销。在声称之前先进行测量。 |
| “我们的代码没有内存错误” | Google 在启用加固 libc++ 时发现了 1000+ 个错误。其他所有人也是。 |
| “C++26 特性尚不可用” | C++20/23 特性是可用的。加固标志适用于任何标准。从那里开始。 |
| “现代 C++ 更难阅读” | std::expected 比检查跨越 5 个输出参数的错误码更具可读性。 |
最佳实践清单
- 使用智能指针表示拥有权,仅将裸指针用于非拥有的观察
- 在函数参数中优先使用
std::span而非指针 + 长度 - 对可能失败的函数使用
std::expected并附带类型化错误 - 使用概念而非 SFINAE 来约束模板
- 在所有构建中启用编译器加固标志和加固 libc++
- 在 CI 中运行 ASan + UBSan;为并发代码添加 TSan
- 在可能的情况下使用
constexpr/consteval(本质上无 UB) - 当忽略返回值很可能是一个错误时,将函数标记为
[[nodiscard]] - 优先使用值语义;使用
std::variant替代union,使用enum class替代enum - 在声明时初始化所有变量
继续阅读
- anti-patterns.md — 完整的传统到现代迁移表(30+ 种模式)
- cpp20-features.md — 概念、ranges、span、format、协程
- cpp23-features.md — expected、print、推导 this、flat_map
- cpp26-features.md — 反射、契约、内存安全改进
- compiler-hardening.md — 标志、清理器、加固 libc++
- safe-idioms.md — 按漏洞类别划分的安全模式