Modern C++(モダン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 | 型安全、暗黙の未定義動作なし |
生の mutex.lock()/unlock() | std::scoped_lock | 例外安全、デッドロックなし |
std::thread | std::jthread | 自動ジョイン、停止トークンサポート |
assert() マクロ | contract_assert(C++26) | ツールから可視、設定可能 |
| 手動CRTP | Deducing this(C++23) | よりシンプル、テンプレートのボイラープレート不要 |
| マクロによるコード生成 | リフレクション(C++26) | ゼロオーバーヘッド、合成可能 |
完全なテーブル(30以上のパターン)については anti-patterns.md を参照してください。
決定木
何をしていますか?
|
+-- 新しいC++コードを書いていますか?
| +-- デフォルトでモダンなイディオム(C++20/23)を使用
| +-- コンパイラがサポートする最新の標準を選択
| +-- 以下の機能ティアを参照
|
+-- 既存コードをモダナイズしていますか?
| +-- Tier 1(C++20/23)の置き換えから開始
| +-- セキュリティへの影響で優先順位付け(メモリ > 型 > スタイル)
| +-- 移行テーブルについては anti-patterns.md を参照
|
+-- セキュリティ・クリティカルなコードですか?
| +-- コンパイラのハードニングフラグを有効化(以下を参照)
| +-- 強化されたlibc++モードを有効化
| +-- CIでサニタイザーを実行
| +-- safe-idioms.md と compiler-hardening.md を参照
|
+-- C++26の機能を使用していますか?
+-- リフレクション: はい、計画を立ててください(GCC 16+)
+-- Contracts: 新しいAPI境界では慎重に
+-- std::execution: エコシステムの成熟を待つ
+-- cpp26-features.md を参照
機能ティア
機能は標準バージョンではなく、今日の実用的な使用可能性に基づいてランク付けされています。
Tier 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 |
Deducing this | CRTP、const/non-constの重複 | C++23 |
std::flat_map | 読み取り重視のstd::map | C++23 |
モナディックstd::optional | optionalに対するネストされたifチェック | C++23 |
cpp20-features.md と cpp23-features.md を参照してください。
Tier 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
compiler-hardening.md を参照してください。
Tier 3: 計画を立てる(C++26、再構成に値する)
リフレクションは、C++26の最も変革的な機能です。以下を排除します:
- シリアライゼーションのボイラープレート(構造体ごとの
to_jsonを1つのジェネリック関数で置き換え) - コードジェネレーター(protobuf codegen、Qt MOC)
- マクロベースの登録と enum-to-string ハック
GCC 16(2026年4月)はリフレクションをマージ済みです。新しいコードが恩恵を受けられるよう計画してください。
Tier 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 を参照してください。
却下すべき合理化
| 合理化 | なぜ間違っているか |
|---|---|
| 「警告なしでコンパイルできる」 | 警告は有効にするフラグに依存します。-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を使用する(設計上未定義動作なし) - 戻り値を無視することがバグである可能性がある場合は、関数に
[[nodiscard]]を付ける - 値セマンティクスを優先し、
unionの代わりにstd::variant、enumの代わりにenum classを使用する - すべての変数を宣言時に初期化する
次に読むもの
- anti-patterns.md — レガシーからモダンへの完全な移行テーブル(30以上のパターン)
- cpp20-features.md — コンセプト、ranges、span、format、コルーチン
- cpp23-features.md — expected、print、deducing this、flat_map
- cpp26-features.md — リフレクション、contracts、メモリ安全性の改善
- compiler-hardening.md — フラグ、サニタイザー、強化されたlibc++
- safe-idioms.md — 脆弱性クラス別のセキュリティパターン