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 块中,在另一种情况下删除了“抛出”并放置了“返回”。

我现在看到我的解决方案很脆弱;它不是面向未来的修改证明。

我想要一个没有这些问题的更好的解决方案。

我有另一个没有上述问题的潜在解决方案,但我想知道其他人对此有何看法。它使用 RAII,特别是一个“Scoped Exit”对象,如果在构造时std::uncaught_exception 真,但在销毁时为真,则隐式触发:

#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 的 lambda,这在您的情况下可能是也可能不是问题。

这是一个简单的函数模板,它接受一个 lambda,运行它并捕获、记录并重新抛出所有异常:

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 ();
});

正如我之前所说,我不知道它如何与您自己的解决方案叠加。请注意,lambda 正在捕获其封闭范围内的所有内容,这非常方便。另请注意,我实际上并没有尝试过,因此可能会导致编译错误、逻辑问题和/或核战争。

我在这里看到的问题是,将其包装到宏中并不容易,并且期望您的同事始终正确地编写[=] {and}部分可能太多了!

出于包装和防白痴的目的,您可能需要两个宏:aTRY_AND_LOG_BEGIN发出第一行直到 lambda 的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 ();

取决于您的观点,这要么是净收益,要么是净损失!(我个人更喜欢直接的函数调用和 lambda 语法,这给了我更多的控制权和透明度。

此外,可以在代码块的末尾写入日志消息;只需切换try_and_log函数的两个参数即可。

于 2013-03-22T05:42:15.153 回答