在我最近编写的一个程序中,我希望在我的“业务逻辑”代码触发第三方或项目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子句,记录错误并重新抛出。在我看来,这是安全的。我知道一般来说,catch-all被认为是错误的,因为根本没有对异常的引用来获取任何有用的信息。但是,我只是要重新抛出它,所以什么都不会丢失。
现在,它本身很好,但其他一些程序员修改了这个程序,最终违反了上述规定。具体来说,他们在一个案例中将大量代码放入try-block,而在另一个案例中删除了'throw'并放置了一个'return'。
我现在看到我的解决方案很脆弱;它不是未来修改的证据。
我想要一个没有这些问题的更好的解决方案。
我有另一个没有上述问题的潜在解决方案,但我不知道别人怎么想。它使用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
函数调用周围添加了最初缺失的catch
/ log
。std::uncaught_exception
状态的构建。这样可以防止在另一个析构函数的“try”块中创建此对象,该析构函数作为异常堆栈展开的一部分触发。答案 0 :(得分:1)
我对你的方法没有评论,但看起来很有趣!我有另一种方式可能也适用于你想要的东西,可能会更通用一些。它需要来自C ++ 11的lambdas,在你的情况下这可能是也可能不是问题。
这是一个简单的函数模板,它接受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从其封闭范围捕获所有,这非常方便。还要注意我实际上没有尝试过这个,因此可能会导致编译错误,逻辑问题和/或核战争。
我在这里看到的问题是将它包装成宏并不容易,并期望你的同事正确地写[=] {
和}
部分并且所有时间都可能太多了!
对于包装和防止白痴的目的,你可能需要两个宏:一个TRY_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
函数的两个参数。