C++17 デストラクタでの例外抑止設計レビュー|破棄時例外の安全設計とレビューアーが確認すべき責務整理
この記事のポイント
- デストラクタ内例外抑止設計のレビュー観点を整理
- 破棄時の二次障害防止をレビューアーがどう読み取るか理解できる
- 例外抑止+通知設計の責務整理をレビューで指摘する実務技術を学ぶ
そもそも「デストラクタでの例外抑止」とは
C++ではデストラクタ内で例外を投げる設計自体は構文上は可能であるが、実務設計上は極力抑止すべき危険設計となる。
- デストラクタ実行中に例外が投げられると、
既に他の例外が飛んでいる最中であった場合、std::terminateが発動する。
通常例外 → デストラクタ例外重畳 → terminate()レビューアーは「このデストラクタが例外を安全に封じ込めているか?」を確認すべきである。
なぜこれをレビューするのか
- デストラクタはスコープ離脱時に自動実行される
- 既にスタックアンワインド中に二重例外が発生すると
プログラムは強制終了(terminate発動) - 破棄責務と障害通知責務を正しく分離設計する必要がある
レビューでは破棄操作は失敗しても破壊責務は完了させる設計になっているかを確認する。
レビューアー視点
- デストラクタ内でthrowしていないか?
- 破棄操作失敗時のログ通知が統合されているか?
- RAII設計の一部として責務分界されているか?
- 二次障害リスクが抑制設計されているか?
- 標準スマートポインタ等の安全設計を活用しているか?
開発者視点
- デストラクタでは原則例外を投げない
- 副作用ある破棄操作は内部catchで封じ込める
- ログ通知は行うが、throwは行わない
- cleanup専用メソッド設計を別途用意する場合もある
良い実装例
例1:内部catchによる例外封じ込め
内部抑止型
class FileWriter {
public:
~FileWriter() {
try {
closeFile();
} catch (const std::exception& e) {
logger.error("FileWriter destruction failed: {}", e.what());
}
}
private:
void closeFile() {
if (failCondition()) {
throw std::runtime_error("Close failure");
}
}
};- 破棄は必ず完遂させる設計
- 通知責務と破棄責務を分離
例2:スマートポインタによる自動破棄
RAII統合型
#include <memory>
void process() {
std::unique_ptr<FILE, decltype(&fclose)> file(fopen("data.txt", "w"), fclose);
}- 標準スマートポインタ型活用により例外封じ込め不要化
レビュー観点
- デストラクタ内でのthrow記述が存在しないか
- 破棄失敗時のログ通知責務が存在するか
- cleanup責務とRAII責務が分離設計されているか
- 二重例外パスを封じる安全設計になっているか
- 標準RAII型適用が徹底されているか
良くない実装例: ケース1(throw漏出)
デストラクタthrow
class Resource {
public:
~Resource() {
close();
}
private:
void close() {
if (failCondition()) {
throw std::runtime_error("close failed");
}
}
@Reviewerデストラクタ内での例外漏出はterminateリスクになります。catch封じ込めを行ってください。};改善例
改善例(catch封じ込め)
~Resource() {
try {
close();
} catch (const std::exception& e) {
logger.error("Destruction failure: {}", e.what());
}
}良くない実装例: ケース2(通知責務不在)
失敗検出不能型
class Cleaner {
public:
~Cleaner() noexcept {
if (failCondition()) {
// 何も出力せず失敗隠蔽
}
@Reviewer失敗時は最低限ログ通知してください。障害診断不能になります。 }
};改善例
改善例(通知統合)
~Cleaner() noexcept {
if (failCondition()) {
logger.error("Cleaner failed during destruction");
}
}良くない実装例: ケース3(手動破棄未整理)
手動破棄責務分散
class Database {
public:
void close() {
if (failCondition()) {
throw std::runtime_error("close failed");
}
}
@Reviewer破棄責務はデストラクタ内封じ込めまたはRAII統合設計で整理してください。外部責務に投げない設計が望ましいです。};改善例
改善例(RAII収束)
~Database() {
try {
close();
} catch (const std::exception& e) {
logger.error("Database close failure: {}", e.what());
}
}PlantUML:破棄責務安全設計フロー
補助設計:cleanup責務外部化パターン
外部明示破棄パターン
class LogFile {
public:
void open() { /* open file */ }
void close() noexcept {
if (failCondition()) {
logger.error("LogFile close failure");
}
}
~LogFile() noexcept { close(); }
};- 破棄責務はスコープ離脱保証しつつ、明示closeも提供可能
二次障害発生ルート整理
| 状態 | 発生時動作 | terminate危険性 |
|---|---|---|
| 平常処理中 | デストラクタ内throw | terminate |
| 例外伝播中 | デストラクタ内throw | terminate即時 |
| デストラクタ封じ込め | catch抑止済み | terminate発生せず |
レビューアーは二重例外リスクを読めることが必須スキルになる。
ロギング統合例
破棄失敗通知設計
~Resource() noexcept {
try {
cleanup();
} catch (const std::exception& e) {
logger.error("Resource destruction failed: {}", e.what());
monitoring.notify("RESOURCE_CLEANUP_FAILURE", e.what());
}
}- ログ+監視通知設計統合
観点チェックリスト
まとめ
レビューアーがデストラクタ例外設計で常に問うべきは
「破棄時の安全保証を設計で守れているか?」
です。
- デストラクタは例外封じ込めを徹底
- 破棄失敗はログ通知責務に分離
- スマートポインタ統合で構造的安全確保
レビュー現場では
「破棄系は二重例外を絶対に許容しない設計文化」
を徹底することで実務現場の安定性を大きく向上させます。
