7

最近作成したプログラムで、「ビジネスロジック」コードがサードパーティまたはプロジェクトAPIで例外をトリガーしたときにログに記録したいと思いました。(明確にするために、APIの使用によって例外が発生したときにログに記録したいと思います。これは実際よりも何フレームも上throwである可能性があり、実際よりも何フレームも下である可能性がありますcatch(例外ペイロードのログが発生する可能性があります。))続く:

void former_function()
{
    /* some code here */
    try
    {
       /* some specific code that I know may throw, and want to log about */
    }
    catch( ... )
    {
       log( "an exception occurred when doing something with some other data" );
       throw;
    }
    /* some code here */
}

つまり、例外が発生した場合は、catch-all句を作成し、エラーをログに記録して、再スローします。私の考えでは、これは安全です。有用な情報を取得するための例外への参照がまったくないため、一般的にキャッチオールは悪いと見なされます。ただし、再スローするだけなので、何も失われません。

さて、それ自体は問題ありませんでしたが、他のプログラマーがこのプログラムを変更し、上記に違反することになりました。具体的には、ある場合には大量のコードをtry-blockに入れ、別の場合には「throw」を削除して「return」を配置しました。

私の解決策はもろいものでした。それは将来の変更に耐えるものではありませんでした。

これらの問題がない、より良い解決策が欲しいです。

上記の問題がない別の解決策がありますが、他の人はそれをどう思っているのでしょうか。これはRAIIを使用します。具体的には、構築ではtrueでstd::uncaught_exception ないが、破棄ではtrueである場合に暗黙的にトリガーする「スコープ出口」オブジェクトです。

#include <ciso646> // not, and
#include <exception> // uncaught_exception

class ExceptionTriggeredLog
{
private:
    std::string const m_log_message;
    bool const m_was_uncaught_exception;
public:
    ExceptionTriggeredLog( std::string const& r_log_message )
      : m_log_message( r_log_message ),
        m_was_uncaught_exception( std::uncaught_exception() )
    {
    }
    ~ExceptionTriggeredLog()
    {
        if( not m_was_uncaught_exception
            and std::uncaught_exception() )
        {
            try
            {
                log( m_log_message );
            }
            catch( ... )
            {
                // no exceptions can leave an destructor.
                // especially when std::uncaught_exception is true.
            }
        }
    }
};

void potential_function()
{
    /* some code here */
    {
       ExceptionTriggeredLog exception_triggered_log( "an exception occurred when doing something with some other data" );
       /* some specific code that I know may throw, and want to log about */
    }
    /* some code here */
}

私は知りたいです:

  • 技術的には、これは堅牢に機能しますか?最初はうまくいくようですが、の使用に関していくつかの注意点があることを私は知っていstd::uncaught_exceptionます。
  • 私が望むことを達成する別の方法はありますか?

:この質問を更新しました。具体的には、次のようになりました。

  • 関数呼び出しの周りに、最初に欠落していたtry/を追加しました。catchlog
  • std::uncaught_exception建設中の状態の追跡を追加しました。これは、このオブジェクトが、例外スタックの巻き戻しの一部としてトリガーされる別のデストラクタの「try」ブロック内に作成される場合を防ぎます。
  • 新しい「potential_function」を修正して、以前のように一時オブジェクトではなく、名前付きオブジェクトを作成しました。
4

1 に答える 1

1

あなたの方法についてはコメントしませんが、面白そうです!私はあなたが望むもののために働くかもしれない別の方法を持っています.もう少し汎用的かもしれません. ただし、C++ 11 のラムダが必要ですが、これは問題になる場合とそうでない場合があります。

これは、ラムダを受け入れて実行し、すべての例外をキャッチしてログに記録し、再スローする単純な関数テンプレートです。

template <typename F>
void try_and_log (char const * log_message, F code_block)
{
    try {
        code_block ();
    } catch (...) {
        log (log_message);
        throw;
    }
}

これを使用する方法 (最も単純な場合) は次のようになります。

try_and_log ("An exception was thrown here...", [&] {
    this_is_the_code ();
    you_want_executed ();
    and_its_exceptions_logged ();
});

前に言ったように、それがあなた自身のソリューションとどのように重なるかわかりません。ラムダは、それを囲むスコープからすべてをキャプチャしていることに注意してください。これは非常に便利です。また、私はこれを実際に試していないことに注意してください。コンパイル エラー、論理的な問題、核戦争が発生する可能性があります。

ここで私が目にする問題は、これをマクロにラップするのは簡単ではないということです。同僚が[=] {and}の部分を正しく書くことを常に期待するのは、時間がかかりすぎるかもしれません!

ラップとばかを証明する目的で、おそらく 2 つのマクロが必要になるでしょう。1 つTRY_AND_LOG_BEGINはラムダの左中括弧までの最初の行を出力するマクロで、もう 1 つTRY_AND_LOG_ENDは右中括弧と括弧を出力するマクロです。そのようです:

#define TRY_AND_LOG_BEGIN(message)  try_and_log (message, [&] {
#define TRY_AND_LOG_END()           })

そして、次のように使用します。

TRY_AND_LOG_BEGIN ("Exception happened!")  // No semicolons here!
    whatever_code_you_want ();
TRY_AND_LOG_END ();

どちらが - あなたの視点に応じて - 純利益または純損失のいずれかです! (個人的には、制御と透明性が向上する単純な関数呼び出しとラムダ構文を好みます。

また、コード ブロックの最後にログ メッセージを書き込むこともできます。関数の 2 つのパラメーターを切り替えるだけtry_and_logです。

于 2013-03-22T05:42:15.153 に答える