【MFC】CException::Delete: なぜ Delete() 呼び出しが必須か

【MFC】CException::Delete: なぜ Delete() 呼び出しが必須か

MFC の例外処理で CException* を受け取ったとき、Delete() を呼ぶ場面と呼んではいけない場面があります。

この記事では、C++ の catch で捕捉した場合は Delete() が必要MFC の CATCH マクロで捕捉した場合は自動削除される、という違いを整理します。


目次

まず結論:Delete() が必要なのは C++ catch で受けたとき

捕捉方法Delete() の扱い理由
catch (CException* e)必要C++ の catch では MFC 例外オブジェクトが自動削除されない
CATCH(CFileException, e)不要。呼ばないMFC マクロがブロック終了時に自動削除する
再スローするその場では呼ばない上位の捕捉側に所有権を渡す
delete e;使わないスタック上やグローバルな例外オブジェクトの可能性がある

Microsoft のドキュメントでも、MFC マクロで捕捉した場合は例外オブジェクトが自動削除され、C++ の catch キーワードで捕捉した場合は自動削除されない、と説明されています。


なぜ delete ではなく Delete() なのか

CException::Delete() は、例外オブジェクトがヒープ上に作られている場合だけ削除する関数です。MFC の例外オブジェクトは、ヒープ上に作られることも、スタック上やグローバルなオブジェクトとして存在することもあります。

// CException::Delete の概念
void CException::Delete()
{
    if (m_bAutoDelete)
    {
        delete this;
    }
}

delete e; を直接呼ぶと、ヒープ上ではないオブジェクトに対して解放してしまう可能性があります。MFC 例外を自分で破棄する必要がある場面では、delete ではなく e->Delete() を使ってください。


C++ catch で捕捉する正しいパターン

C++ 標準の try-catch で MFC 例外を受ける場合は、CException* として捕捉し、処理が終わったら Delete() を呼びます。

try
{
    CFile file;
    if (!file.Open(strPath, CFile::modeRead))
    {
        AfxThrowFileException(CFileException::fileNotFound,
                              -1,
                              strPath);
    }
}
catch (CFileException* e)
{
    TCHAR szError[256]{};
    e->GetErrorMessage(szError, 256);
    AfxMessageBox(szError);

    // C++ catch では自動削除されないため必要
    e->Delete();
}

MFC CATCH マクロでは Delete() を呼ばない

MFC の TRY / CATCH / END_CATCH マクロを使う場合は、マクロ側が例外オブジェクトを削除します。ここで e->Delete() も呼ぶと、二重に解放することになります。

TRY
{
    CFile file;
    file.Open(strPath, CFile::modeRead);
}
CATCH(CFileException, e)
{
    TCHAR szError[256]{};
    e->GetErrorMessage(szError, 256);
    AfxMessageBox(szError);

    // ここでは e->Delete() を呼ばない。
    // CATCH マクロが自動削除する。
}
END_CATCH

TRY / CATCH と C++ 標準の try-catch の違いは、【MFC】try-catch (C++) vs CATCH (MFCマクロ) の違いで比較しています。


よくある間違い

// NG: C++ catch で Delete() を呼んでいない
catch (CException* e)
{
    AfxMessageBox(_T("エラーが発生しました"));
    // e->Delete() がないためリーク要因になる
}

// NG: delete を直接呼んでいる
catch (CException* e)
{
    delete e;  // ヒープ上とは限らないため危険
}

// NG: MFC CATCH マクロ内で Delete() を呼んでいる
CATCH(CFileException, e)
{
    e->ReportError();
    e->Delete();  // 呼ばない。マクロ側が自動削除する
}
END_CATCH

再スローするときは Delete() しない

例外を上位へ再スローする場合は、その場で Delete() を呼びません。上位の catch で最終的に処理するときに削除します。

try
{
    LoadData();
}
catch (CException* e)
{
    LogException(e);
    throw;  // この場では Delete() しない
}

まとめ

  • C++ catch (CException* e) では、処理後に e->Delete() を呼びます
  • MFC CATCH マクロでは、例外オブジェクトは自動削除されるため Delete() を呼びません
  • delete e; ではなく e->Delete() を使います
  • 再スローする場合は、その場で Delete() しません
目次