古い 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() の扱いです。MFC の 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()を呼びます - 移行時は、構文だけでなく例外オブジェクトの所有権も確認します
