考虑以下资源管理类
class FooResouce
{
public:
explicit FooResouce(T arg_to_construct_with)
{
m_foo = create_foo_resouce(arg_to_construct_with);
if(m_foo == nullptr)
{throw SomeException(get_foo_resource_error(), arg_to_construct_with);}
}
// Dtor and move operations
// Other FooResource interaction methods
private:
foo_resource_t* m_foo;
};现在,当我们决定捕获异常并格式化错误消息时,很容易知道是什么导致了基本级别的异常,但是我们不知道在上层触发异常的位置。在这里,上层指的是试图创建FooResource的函数,或者该堆栈框架之上的任何函数。如果需要的话,如何在错误中添加上下文:
pushContext函数。此函数将使用线程本地存储存储上下文。发布于 2019-02-02 17:29:27
虽然该解决方案与您的第三个需求3:无捕获和重新抛出相冲突,但我建议使用std::nested_exception和宏解决方案,因为这似乎为当前问题提供了一个合理的解决方案,至少对我来说是这样。我希望这个太长的答案能帮到你。
1.使用std::nested_exception进行错误处理
首先,我们可以使用std::nested_exception递归地嵌套异常。粗略地说,我们可以通过调用std::throw_with_nested向该类添加任意类型的异常。这使我们能够用相当简单的代码携带抛出的异常的所有信息,只需在上层的每个省略号捕获处理程序catch(…){ }中抛出每个异常。
例如,下面的函数h抛出一个聚合用户定义的异常SomeException和std::runtime_error的std::nested_exception。
struct SomeException : public std::logic_error {
SomeException(const std::string& message) : std::logic_error(message) {}
};
[[noreturn]] void f(){
std::throw_with_nested(SomeException("error."));
}
[[noreturn]] void g()
{
try {
f();
}
catch (...) {
std::throw_with_nested(std::runtime_error("Error of f."));
}
};
[[noreturn]] void h()
{
try {
g();
}
catch (...) {
std::throw_with_nested(std::runtime_error("Error of g."));
}
}定位异常(基本级别)
将所有这些std::throw_with_nested替换为下面的函数throw_with_nested_wrapper,通过宏THROW_WITH_NESTED,我们可以记录文件名和出现异常的行号。众所周知,__FILE__和__LINE__是由C++标准预定义的.因此,宏THROW_WITH_NESTED有一个关键角色来添加以下位置信息:
// "..." are arguments of the ctor of ETYPE
// and the first one must be a string literal.
#define THROW_WITH_NESTED(ETYPE, ...) \
throw_with_nested_wrapper<ETYPE>(__FILE__, __LINE__, __VA_ARGS__);
template<typename E, typename ...Args>
[[noreturn]]
void throw_with_nested_wrapper(
char const* fileName,
std::size_t line,
const std::string& message,
Args&& ...args)
{
auto info = std::string(fileName)
+ ", l." + std::to_string(line) + ", "
+ message;
std::throw_with_nested(E(info, std::forward<decltype(args)>(args)...));
};定位异常(上层)
如果我们必须获取在上层触发异常的位置的信息,那么以下重用上述宏HOOK的宏THROW_WITH_NESTED将适用于我们:
#define HOOK(OPERATION) \
[&]() \
{ \
try{ \
return OPERATION; \
} \
catch(...){ \
auto info = std::string(#OPERATION) + ", upper level."; \
THROW_WITH_NESTED(std::runtime_error, info); \
} \
}()最后,将前三个函数f、g和h重写并简化如下:
[[noreturn]] void f(){
THROW_WITH_NESTED(SomeException, "SomeException, fundamental level.");
}
void g(){
HOOK(f());
};
void h(){
HOOK(g());
}提取误差信息
提取嵌套异常的所有解释信息是一项简单的任务。将被捕获的异常在最外层的try-catch块中传递到下面的函数output_exceptions_impl中,我们可以这样做。每个嵌套异常都可以由std::nested_exception::rethrow_nested递归抛出。由于这个成员函数在没有存储异常时调用std::terminate,因此我们应该应用dynamic_cast来避免它,如this post中所指出的那样:
template<typename E>
std::enable_if_t<std::is_polymorphic<E>::value>
rethrow_if_nested_ptr(const E& exception)
{
const auto *p =
dynamic_cast<const std::nested_exception*>(std::addressof(exception));
if (p && p->nested_ptr()){
p->rethrow_nested();
}
}
void output_exceptions_impl(
const std::exception& exception,
std::ostream& stream,
bool isFirstCall = false)
{
try
{
if (isFirstCall) { throw; }
stream << exception.what() << std::endl;
rethrow_if_nested_ptr(exception);
}
catch (const std::runtime_error& e) {
stream << "Runtime error: ";
output_exceptions_impl(e, stream);
}
/* ...add further catch-sections here... */
catch(...){
stream << "Unknown Error.";
}
}顺便说一句,在最外层的地方,显式try-catch块相当冗长,因此我通常使用this post中提出的以下宏:
#define CATCH_BEGIN try{
#define CATCH_END(OSTREAM) } catch(...) { output_exceptions(OSTREAM); }
void output_exceptions(std::ostream& stream)
{
try {
throw;
}
catch (const std::exception& e) {
output_exceptions_impl(e, stream, true);
}
catch (...) {
stream << "Error: Non-STL exceptions." << std::endl;
}
}然后,从h抛出的所有异常都可以通过以下代码进行跟踪和打印。在代码的正确行插入宏THROW_WITH_NESTED、HOOK、CATCH_BEGIN和CATCH_END,我们可以在每个线程中找到异常:
CATCH_BEGIN // most outer try-catch block in each thread
...
HOOK(h());
...
CATCH_END(std::cout)然后,我们得到以下输出,其中文件名和行号只是一个例子。所有可获得的资料均记录在案:
运行时错误: prog.cc,1.119,h(),较高级别。 运行时错误: prog.cc,l.113,g(),较高级别。 运行时错误: prog.cc,l.109,f(),较高级别。 逻辑错误: prog.cc,1.105,SomeException,基本级别。
2.如属FooResouce
第一项要求是
让我们定义以下特殊的异常类SomeException,它包含一些可选的上下文信息和成员函数getContext来获取它:
class SomeException : public std::runtime_error
{
std::string mContext;
public:
SomeException(
const std::string& message,
const std::string& context)
: std::runtime_error(message), mContext(context)
{}
const std::string& getContext() const noexcept{
return mContext;
}
};将一个新的参数context添加到FooResouce::FooResouce并将throw替换为THROW_WITH_NESTED,我们可以在上面的错误处理框架中传递第一个需求:
class FooResouce
{
public:
FooResouce(
T arg_to_construct_with,
const std::string& context)
{
m_foo = create_foo_resouce(arg_to_construct_with);
if(!m_foo){
THROW_WITH_NESTED(SomeException, "Ctor failed.", context);
}
...
}
...
};下一首,
但是我们没有关于异常是在高层触发的信息。这里的上层指的是试图创建FooResource的函数,
使用FooResource创建每个HOOK,我们可以获得关于ctor在上层失败的位置的信息。呼叫方如下所示。这样,所有错误信息(包括消息、上下文及其位置)都将在每个线程中得到澄清。
CATCH_BEGIN // most outer try-catch block in each thread
...
auto resource = HOOK(FooResouce(T(), "context"));
...
CATCH_END(std::cout)最后,
虽然我不知道这个需求的细节,但是由于我们可以按下面的方式在SomeException::getContext中调用output_exceptions_impl并从每个抛出的SomethingExceptions中获取所有上下文,所以我认为我们也可以这样存储它们:
void output_exceptions_impl(
const std::exception& exception,
std::ostream& stream,
bool isFirstCall = false)
{
...
catch (const SomeException& e) { // This section is added.
stream
<< "SomeException error: context:"
<< e.getContext() << ", "; // or pushContext?
output_exceptions_impl(e, stream);
}
...
}https://stackoverflow.com/questions/54490757
复制相似问题