この記事の途中に、以下の記事の引用を含んでいます。
C++: “We have try…finally at home”
他言語と比較!C++に「finally」が無い理由に迫る
プログラミング言語を学んでいると、例外処理(try, catch)に続く「finally」句という存在に出会うことが多いと思います。
Java、C#、Python、JavaScriptなど主要言語では、「try { … } finally { … }」の形で、エラーの有無に関わらず必ず実行したい処理を記述できます。
一方、C++では「finally」を持ちません。
今回紹介する記事 C++: “We have try…finally at home” は、この「C++におけるfinally的処理」の設計背景と、他言語との意外な違いを専門的に解説しています。
この記事を通じて、C++ならではの「リソース管理」や「安全な例外処理」の文化的背景も考えます。
C++に「finally」が存在しない、その代用とは?
記事の冒頭では、以下のように他言語との比較がまとめられています。
Many languages¹ that have exceptions also have a finally clause, so you can write
try { ⟦ stuff ⟧ } finally { always(); }
このようなfinally句は、Java, C#, Python, JavaScriptにありますが、
“…but not C++.”
と圧倒的にシンプルな事実で指摘されています。
では、C++で「finallyと同じことをしたい」場合、どうするべきなのか。
記事はこの点について次のように述べています。
In C++, the way to get a block of code to execute when control leaves a block is to put it in a destructor, because destructors run when control leaves a block.
つまり、C++ではスコープを抜けるときに必ず実行してほしい処理は「デストラクタ」に書くというのが慣習なのです。
より具体的には、Windows Implementation Library(WIL)で用意されている wil::scope_exit の使い方が例示されています。
cpp
auto ensure_cleanup = wil::scope_exit([&] { always(); }); //後始末をLambdaで指定
// ⟦ ここにメインの処理 ⟧
この書き方は、scope_exit がRAII(Resource Acquisition Is Initialization)パターンに則っていて、スコープ終了時にLambdaをデストラクタ内で実行してくれます。
「finally」と「デストラクタ」の微妙な落とし穴 ― 例外内例外の危険
単に「finally的」なことができるといっても、重要なのは細部です。C++のデストラクタによるfinally的機能と他言語のfinallyでは、例外が絡んだときの振る舞いに決定的な違いがあります。
記事は一例としてこう述べます。
If control leaves the block without an exception, then any uncaught exception that occurs in the finally block or the destructor is thrown from the try block. All the languages seem to agree on this.
If control leaves the block with an exception, and the finally block or destructor also throws an exception, then the behavior varies by language.
後者の場合、
In Java, Python, JavaScript, and C# an exception thrown from a finally block overwrites the original exception, and the original exception is lost.
つまり、Java/Python/C#などでは「finallyでの例外が先に投げられた例外を上書き」してしまう。
これは元々発生した深刻なエラー情報が消えやすい、”危険な落とし穴”だといえるでしょう。
一方、C++はさらに厳格です。
In C++, an exception thrown from a destructor triggers automatic program termination if the destructor is running due to an exception.
C++でデストラクタ内から例外を再度投げると「std::terminate()」によるプログラム即時終了が発生します(発生源が元の例外処理内だった場合)。
この点に関する記事の主張は非常に示唆的です。
So C++ gives you the ability to run code when control leaves a scope, but your code had better not allow an exception to escape if you know what’s good for you.
すなわち、
「C++のデストラクタは、絶対にthrowしない設計にすべき」
という警鐘を鳴らしているのです。
C++流リソース管理の意義と、設計指針
他言語の「finally句」は「終了直前に安全確実に後始末をするため」の標準構文ですが、
C++はRAII(スコープベースのリソース管理)によって、より自動的で静的なリソース回収を実現します。
RAIIパターンでは、ファイル・メモリ・ロックといったリソースはオブジェクト寿命とスコープ管理によって回収されるので、
「うっかり後始末を忘れた」「例外時にリソースリークした」という事故を根本的に減らせます。
ただし、RAIIを正しく運用するためには、「デストラクタは失敗しない」
「デストラクタは絶対にthrowしない」を守る必要があるわけです。
この点を意識せずJavaやPython感覚でfinally的に「とりあえず片付けをする(ついでに例外も投げる)」と、C++では致命的なバグや異常終了の元になる危険があります。
批評と現代的視点:C++は「本当につらい」のか?
初心者やマルチ言語プログラマからは
「C++にもfinallyがあれば良いのに」
「RAIIは分かりにくい」
という声が必ず上がります。
しかし、この記事が示しているように、C++流の設計は決して“手抜き”や“ケチ”によるものではありません。
むしろ、それぞれの言語が持つ「エラー通知と後始末の一貫性・安全性」を最大化する手法なのです。
C++コミュニティでは「例外安全」の理論研究が長年行われており、例えば「強例外保証(strong exception safety)」や「no-throw保証(nothrow)」といった設計パターンが生まれています。
また、C++17以降ではstd::unique_ptr、std::lock_guardなど、RAII思想を体現したユースケース用の便利クラスも充実しました。
現代のC++において、「デストラクタでthrowしない」を守り、「オブジェクト寿命で後始末を徹底する」というスタイルは最も安全なベストプラクティスとなっています。
これこそがC++本来の力であり、他言語の「finally句」と単純に比較してはいけないポイントです。
まとめ:言語間の作法の違いを理解し、「安全な例外処理」と向き合う
C++が「finally」を持たないという話から出発し、
RAII流リソース管理・デストラクタ設計の責任まで掘り下げてみました。
他言語とC++で「安全な後始末」を比べると、必ずしもどちらが優れていると言い切ることはできませんが、
大切なのは「それぞれ最善の設計思想が背景にある」「作法を誤ると深刻な不具合につながる」という点です。
- C++ではRAIIパターンを意識してオブジェクト寿命とスコープ管理による「自動後始末」を徹底しましょう。
- どうしてもスコープ脱出時の処理が必要なら、
scope_exitや独自RAIIラッパーを使うのがベストです。 - デストラクタでthrowしない原則は絶対に守ること!
あなたが言語を横断して開発する場合、
「なぜこの言語はこういう作法なのか」を意識し、その違いを尊重した設計を目指してください。
categories:[technology]

コメント