現代 C++ (Modern 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 | 消除記憶體洩漏、雙重釋放 |
| 原始擁有者指標 (Raw owning pointers) | 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 | 型別安全、無未定義行為 |
原始 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 中執行消毒器 (Sanitizers)
| +-- 參閱 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 + 手動 join | C++20 |
| 指定初始化 (Designated initializers) | 位置式結構體初始化 | C++20 |
std::expected<T,E> | 錯誤碼、邊界例外 | C++23 |
std::print / std::println | printf、std::cout << | C++23 |
推導 this (Deducing this) | CRTP、const/non-const 重複 | C++23 |
std::flat_map | std::map (適用於讀取密集型場景) | C++23 |
std::optional 的單子操作 (Monadic) | 對 optional 的巢狀 if 檢查 | 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 中的消毒器 (Sanitizers) — 最低限度使用 ASan + UBSan;並行程式碼再加入 TSan
- 警告旗標 —
-Wall -Wextra -Wpedantic -Werror
第 3 級:為它做規劃 (C++26,值得圍繞其重構)
反射 (Reflection) 是最具變革性的 C++26 功能。它消除了:
- 序列化樣板程式碼 (一個通用函式取代每個結構體的
to_json) - 程式碼生成器 (protobuf codegen、Qt MOC)
- 基於巨集的註冊與枚舉轉字串的 hack
GCC 16 (預計 2026 年 4 月) 已合併反射功能。規劃新的程式碼以利用此優勢。
第 4 級:觀察中 (C++26,需要成熟)
- 契約 (Contracts) (
pre/post/contract_assert) — 比assert()更好,但缺乏虛擬函式支援且編譯器支援有限。謹慎用於新的 API 邊界。 - std::execution (senders/receivers) — 強大的非同步框架,但學習曲線陡峭、沒有內建排程器、文件不足。等待生態系統成熟。
請參閱 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 取得完整指南。
應拒絕的常見推論
| 推論 | 為什麼是錯的 |
|---|---|