古いMFCプロジェクトを引き継ぐと、例外処理にTRY / CATCH / END_CATCHというMFC独自マクロが使われていることがあります。この記事では、C++ 標準のtry-catchとMFCマクロの違いを、構文、CExceptionの寿命管理、移行判断の観点で整理します。
まず結論:新規コードはtry-catch、既存マクロは無理に壊さない
| 状況 | 選ぶ書き方 | 理由 |
|---|---|---|
| 新規に例外処理を書く | C++ 標準のtry-catch | 現在のC++ と相性がよく、std::exceptionなども扱いやすい |
| 既存コードにMFCマクロが大量にある | すぐ全置換しない | 動いている例外経路を壊すリスクがあるため、変更範囲を絞る |
| MFC例外をC++ catchで受ける | catch (CException* e) | MFCのthrow関数は例外オブジェクトをポインタで投げる |
| CException::Delete() の扱い | マクロ版とC++ 版で異なる | MFCマクロは自動削除、C++ catchは自分で削除する |
一番間違えやすいのはCException::Delete() の扱いです。CATCHマクロで捕捉したオブジェクトはマクロ側が後始末をするのでe->Delete() は呼びませんが、C++ のcatchでCException* を受けたときは自分でe->Delete() を呼ぶ必要があります。捕捉の仕方で削除の責任が逆になる点を押さえておきます。
CException::Delete() の所有権ルールは、【MFC】CException::Delete: なぜDelete() 呼び出しが必須かで詳しく整理しています。
MFCマクロの構文
MFCの例外マクロは、C++ 例外処理構文が一般化する前から使われていた互換用の書き方です。構文は次のようになります。
TRY
{
CFile file;
file.Open(_T("data.dat"), CFile::modeRead);
// ... ファイル操作 ...
}
CATCH(CFileException, e)
{
// e は CFileException* として使える
TCHAR szError[256]{};
e->GetErrorMessage(szError, 256);
AfxMessageBox(szError);
// ここで e->Delete() は呼ばない。
// CATCH マクロが例外オブジェクトを自動削除する。
}
END_CATCH
- CATCHの引数は、例外クラス名とポインタ変数名です
- 例外オブジェクトはCFileException* のようにポインタとして扱います
- CATCHマクロで捕捉した例外は、マクロの終了時に自動削除されます
- AND_CATCHで複数のMFC例外型を捕捉できます
- END_CATCHで例外処理ブロックを閉じます
C++ 標準のtry-catchで書く場合
同じ処理をC++ 標準のtry-catchで書くと、次のようになります。MFCの例外をCException* として捕捉した場合は、最後にDelete() を呼びます。
try
{
CFile file;
if (!file.Open(_T("data.dat"), CFile::modeRead))
{
AfxThrowFileException(CFileException::fileNotFound,
-1,
_T("data.dat"));
}
// ... ファイル操作 ...
}
catch (CFileException* e)
{
TCHAR szError[256]{};
e->GetErrorMessage(szError, 256);
AfxMessageBox(szError);
// C++ catch では自動削除されないため必要
e->Delete();
}
catch (const std::exception& ex)
{
CString msg(ex.what());
AfxMessageBox(msg);
}
C++ 標準のtry-catchでは、MFC例外だけでなくstd::exception系の例外も同じ構文で扱えます。新規コードではこちらに寄せる方が読みやすく、MFC以外のC++ コードとも混在しやすくなります。
比較表
| 観点 | MFCマクロ | C++ 標準try-catch |
|---|---|---|
| 構文 | TRY / CATCH / AND_CATCH / END_CATCH | try / catch |
| MFC例外の受け取り方 | CATCH(CFileException, e) | catch (CFileException* e) |
| CException::Delete() | 呼ばない。マクロ側が自動削除する | CException* を捕捉したら呼ぶ |
| std::exceptionの捕捉 | 対象外 | catch (const std::exception&) で捕捉できる |
| 複数型の捕捉 | AND_CATCHで連鎖 | 複数のcatchブロック |
| 新規コードでの読みやすさ | レガシー色が強い | 現代C++ として読みやすい |
| 主な使いどころ | 既存MFCコードの保守 | 新規コード、移行後のコード |
CException::Delete() の判断を間違えない
CException::Delete() は、例外オブジェクトがヒープ上に作られている場合だけ安全に削除するためのMFCの関数です。delete e; を直接呼ぶのではなく、必要な場面ではe->Delete() を使います。
| 捕捉方法 | Delete() は必要か | 理由 |
|---|---|---|
| CATCH(CFileException, e) | 不要。呼ばない | MFCマクロが自動的に呼ぶ |
| catch (CFileException* e) | 必要 | C++ catchキーワードでは自動削除されない |
| catch (const std::exception& ex) | 不要 | MFCのCException* ではない |
この違いを混同すると、メモリリークや二重解放につながります。古いコードを移行するときは、単純にCATCHをcatchへ置き換えるだけでなく、Delete() の責任がどちらにあるかを必ず確認します。
マクロから標準try-catchへの書き換え手順
書き換えは機械的に見えますが、Delete() の責任が変わる点だけは必ず確認します。
| MFCマクロ | C++ 標準 | 注意点 |
|---|---|---|
| TRY { | try { | ブロック構造を確認する |
| CATCH(CXxxException, e) { | catch (CXxxException* e) { | 標準catchでは最後にe->Delete() を追加する |
| AND_CATCH(CYyyException, e) { | catch (CYyyException* e) { | 複数のcatchに分ける |
| END_CATCH | 削除 | } の対応を崩さない |
| THROW(pException) | throw pException; | 所有権が移る前提を確認する |
| THROW_LAST() | throw; | 現在の例外処理中だけ使える |
既存コードが安定しているなら、プロジェクト全体を一気に置き換える必要はありません。新規に触るファイル、または例外処理を修正するファイル単位で標準try-catchに寄せるのが現実的です。
次に読む記事
まとめ
- 新規コードはC++ 標準のtry-catchを使う方が読みやすいです
- 既存のMFCマクロは、安定しているなら無理に一括置換しません
- MFCマクロのCATCHでは、例外オブジェクトは自動削除されます
- C++ catch (CException* e) では、自分でe->Delete() を呼びます
- 移行時は、構文だけでなく例外オブジェクトの所有権も確認します
