【MFC】try-catch (C++) vs CATCH (MFCマクロ) の違い

【MFC】try-catch (C++) vs CATCH (MFCマクロ) の違い

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