この記事のポイント

  • noexcept指定の設計意図と適用責務をレビューアーが読み取る視点を体系化
  • パフォーマンス最適化、安全性強化、契約宣言の責務整理をレビューで指摘可能にする
  • noexcept適用の落とし穴と危険適用を現場レビュー技術として習得する

そもそもnoexceptとは何か

C++11以降、関数が「例外を投げないことを宣言する」仕様が導入された。
C++17ではこのnoexcept指定が特にムーブ構築・ムーブ代入最適化の中核であり、
設計レベルでも安全性担保に極めて重要となっている。

void foo() noexcept { ... }
noexceptの意味
  • 呼び出し側契約 → 「ここは絶対に例外を投げないと信じて良い」
  • コンパイラ最適化ヒント → スタック展開コスト削減やmove優先選択
  • 設計責任宣言 → 設計者が安全性を担保している明示契約

なぜこれをレビューするのか

  • 例外安全性保証をレビュー可能にする役割
  • 設計責務が言語仕様で強制可視化される重要契約領域
  • 無自覚なnoexcept指定はterminate誘発リスクに直結
  • パフォーマンスと安全性のトレードオフをレビューで判別必要

レビューアーはnoexceptはつけた/つけなかったの表面的確認ではなく
「ここに付与する設計意図が正当か?」
を読み解く技術を持つ必要がある。

レビューアー視点

  • この関数は本当にthrowしないのか?
  • 設計責務が契約保証と一致しているか?
  • ムーブ最適化対象での正当な指定か?
  • 内部で呼び出す他関数のthrow可能性まで精査しているか?
  • 汎用テンプレート展開時のSFINAE破綻が無いか?

開発者視点

  • 原則:安全確認が取れたものだけnoexcept指定する
  • パフォーマンス目的のnoexcept付与は根拠確認必須
  • ライブラリ利用関数は内部throw可能性を文書化調査
  • noexcept(false)による明示化も活用
  • noexcept(auto式)の式依存評価も適用可能

noexceptが影響する代表的設計領域

設計領域 noexcept影響内容
ムーブ最適化 noexcept付きムーブが優先使用される
標準コンテナ動作 vector等での内部再配置moveが有無で分岐
swap最適化 noexcept swapが優先利用される
テンプレート推論 noexcept(auto)式評価でテンプレート解像度に影響
例外安全設計 契約違反時にterminate発動が設計責務化

レビューアーはこれら全てをコード脈絡から読み取る技術が求められる。

良い実装例

例1:純粋スカラ操作関数

純粋計算関数はnoexcept明示
int add(int a, int b) noexcept {
    return a + b;
}
  • 論理的に例外発生可能性ゼロ
  • レビューアーが自信を持って指定を許容できる典型

例2:ムーブコンストラクタ最適化対象

ムーブ最適化設計
class MyData {
public:
    MyData(MyData&& other) noexcept
        : buffer_(std::move(other.buffer_)) {}

private:
    std::vector<int> buffer_;
};
  • vectorは自身が例外安全保証済みmove
  • 内部構成子の安全性を読んだ上でnoexcept指定

例3:noexcept(auto式)による導出指定

noexcept式評価
template<typename T>
void process(T&& t) noexcept(noexcept(t.execute())) {
    t.execute();
}
  • テンプレートで安全性を委譲評価
  • レビューアーが汎用コード安全性を読み取れる設計

レビュー観点

  • noexcept指定は契約責務と整合しているか
  • 内部呼び出し可能性まで含め安全性確認されているか
  • パフォーマンス最適化対象設計に統合されているか
  • noexcept(auto式)等式評価を適切活用しているか
  • 例外発生→terminateを設計想定内で整理しているか

良くない実装例: ケース1(表層指定ミス)

throw可能性無視型
void dangerous() noexcept {
    mayThrow(); // 内部でthrow可能
@Reviewer
内部呼び出しmayThrow()が例外可能性を持つのにnoexcept指定しています。設計違反です。noexcept外してください。
}

改善例

改善例(契約整合)
void dangerous() {
    mayThrow();
}

良くない実装例: ケース2(テンプレート展開破綻)

テンプレート破綻型
template<typename T>
void execute(T&& t) noexcept {
    t.run();
@Reviewer
呼び出し先run()の例外可能性確認無しにnoexcept指定しています。noexcept(noexcept(t.run()))形式に整理してください。
}

改善例

改善例(式評価指定)
template<typename T>
void execute(T&& t) noexcept(noexcept(t.run())) {
    t.run();
}

良くない実装例: ケース3(パフォーマンス誤解利用)

パフォーマンス目当て誤用
class MyData {
public:
    MyData(MyData&& other) noexcept {
        buffer_ = other.buffer_;  // 実はコピー
@Reviewer
ムーブ処理内で実質的にコピー代入しており例外可能性を残しています。noexcept指定は取り消してください。
} private: std::vector<int> buffer_; };

改善例

改善例(内部構成子正読)
MyData(MyData&& other) noexcept
    : buffer_(std::move(other.buffer_)) {}

良くない実装例: ケース4(契約破綻設計)

API設計崩壊例
void saveFile(const std::string& path) noexcept {
    std::ofstream ofs(path);  // 実はここで例外発生可能
@Reviewer
ファイル出力は例外可能です。noexcept指定は設計破綻要因になります。外してください。
}

改善例

改善例(契約整理)
void saveFile(const std::string& path) {
    std::ofstream ofs(path);
}

PlantUML:noexceptレビュー責務整理フロー

UML Diagram

noexcept設計レビューでの主な誤解一覧

誤解 実態 レビュー指摘内容
noexcept付けるほど高性能 条件次第で逆効果もある 実装脈絡評価が必要
内部throw考慮不要 全内部呼出も評価対象 内部例外流入をレビュー
テンプレート展開安全 型依存で破綻する noexcept(auto式)活用確認
すべてつけるべき 安全設計できた箇所のみ 契約設計読み取り

ロギング統合例

terminate発動前ログ設計
try {
    criticalOp();
} catch (const std::exception& e) {
    logger.fatal("critical failure: {}", e.what());
    std::terminate();
}
  • noexcept→terminate発動前にログ通知責務を挟む文化形成

観点チェックリスト

まとめ

レビューアーがnoexcept設計で常に問うべきは
「この例外安全宣言は本当に契約として守られているか?」
です。

  • 例外安全保証 → 契約レベルで可視化
  • パフォーマンス最適化 → noexcept安全領域で限定活用
  • terminate誘発 → 設計上の最終防御線に配置

レビュー現場では
「このnoexcept指定は本当に内部構成子まで安全に読めてますか?」
と問い続けるレビュー文化がパフォーマンスと安全性を同時に高めます。