複数のエラーを上流に知らせる方法(C++)

クラス構造を作成するときに、結構困るのが、エラー情報の伝達方法。
エラーの有無が重要な場合や、エラー情報が1つだけの場合だったら、関数の戻り値にboolや文字列クラスを使用することで、なんとかなるかも知れないけど、エラーが起きたら、処理を中断することばかりではない。
処理を継続して、最終的に複数のエラーを伝えたいこともあるし、下流で起こったエラーを上流に伝えるときに、さらにエラー情報を補足したい事もよくある。
また、エラー処理の定石の try~catch 句は、こういう問題を解決するためのものではない。

こんな時に利用できるのが次みたいなクラス。
#include <list>
#define MAX_ERROR_LENGTH 511
class ErrorQueue
{
    ErrorQueue(){}
    virtual ~ErrorQueue() = 0;    // オーバーライドを要求
protected:
    std::list<CString> m_errorQueue;    // エラー情報を保存
    void SetError(const char *format, ...)
    {
        char buffer[MAX_ERROR_LENGTH+1];
        va_list errmsg;
        va_start(errmsg, format);
        vsprintf(buffer, format, errmsg);
        va_end(errmsg);
        m_errorQueue.push_back(buffer);
    }
public:
    bool GetNextError(CString& strError)    // エラー情報を取り出す
    {
        if(m_errorQueue.empty())
            return false;
        strError = m_errorQueue.front();
        m_errorQueue.pop_front();
        return true;
    }
};

このクラスでは、STLのリストを使用して、エラー情報をキャッシュしている。
STLのqueueを使っているわけじゃないけど、外部クラスへは、キューに近いインターフェースを公開しているのでクラス名にはQueueを付けた。
ErrorQueueクラスは、純粋仮想関数なので、他のクラスに継承させてから使用しなければいけない。
基本的には、このようなエラー処理が必要になった既存のクラスに多重継承で(無理矢理 ^^;) くっつける。

派生クラスの中でエラーが発生したら、何回でも SetError() 関数を呼び出して、リストにエラー情報を貯めておくことが出来る。
↓はエラー登録コードのイメージです。
bool CXYZ::DoAllProcesses()
{
    bool ok = true;
    for(int i=0; i<count; i++)
    {
        CString& strFileName = m_fileList[i];
        if(!DoProcess(strFileName ))
        {
            SetError("処理に失敗しました: %s", strFileName);
            ok = false;
        }
    }
    return ok; // 成功か失敗かは上流に知らせる
}

クライアントコードからは、次のように while ループで全てのエラー情報を取得できる。
...
if(!xyz.DoAllProcesses())
{
    // 全てのエラー情報をファイルに書き出す
    CString errMsg;
    while(obj.GetNextError(errMsg))
    {
        logFile.Write("This Class Name : %s\n", errMsg);
    }
    AfxMessageBox("エラーが発生しました。ログファイルを確認して下さい。");
}
...

また、次のように直前のメッセージに追記する関数をErrorQueueクラスに追加して、その次のようなマクロを作って、エラーの発生場所と時間なんかを自動的に書き出してくれるようにすると、後々楽かも。
    // 直前のエラー情報に追記
    void AppendError(const char *format, ...)
    {
        char buffer[MAX_ERROR_LENGTH+1];
        va_list errmsg;
        va_start(errmsg, format);
        vsprintf(buffer, format, errmsg);
        va_end(errmsg);
        m_errorQueue.back() += buffer;
    }
// タイムスタンプ・ファイル名・行番号を付加
#define SETERROR SetError("%s %s, %s(%d), " \
    , __DATE__, __TIME, __FILE__, __LINE__); \
    AppendError

また、m_errorQueue をスタティック変数にすれば、全てのエラーを時系列で取得することも出来るし、キューに入れずにファイルに書き出すようにすれば、ログファイルの作成にも役立つかも。

ANSI準拠にするには、CString を std::string に変えるだけでOKなはず。
MFCを使っていると、文字列クラスは必ず CString になってしまう。
しかも、無理に std::string を使おうと思うと、必ずごちゃ混ぜになって、かなり醜いコードになってしまう (;_;)

[PR]
by isoq | 2005-04-21 18:09 | C/C++/Win32
e87.com(千趣会イイハナ) 花を贈るなら日比谷花壇
<< 自宅LAN内部のPCから外部に... HPデザイン変更 >>