C++ 从异常恢复上下文
考虑以下资源管理类C++ 从异常恢复上下文,c++,exception,C++,Exception,考虑以下资源管理类 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_f
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
函数。此函数将使用线程本地存储来存储上下文尽管此解决方案与您的第三个要求3:无捕获和重新捕获冲突,但我建议使用
std::nested_exception
和宏的解决方案,因为至少对我来说,这似乎为当前问题提供了合理的解决方案。
我希望这个太长的答案能帮助你
1.使用
std::nested_异常处理错误
首先,我们可以使用递归嵌套异常。
粗略地说,我们可以通过调用std::throw\u with_nested
向这个类添加任意类型的异常。
这使我们能够用一个相当简单的代码来携带抛出异常的所有信息,只需在上层的每个省略号catch处理程序catch(…){}
中通过嵌套的std::throw_抛出每个异常
例如,下面的函数h
抛出一个std::nested_异常
,它聚合了用户定义的异常SomeException
和std::runtime_error
:
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."));
}
}
查找异常(基本级别)
通过宏throw\u with_nested
,我们可以记录发生异常的文件名和行号,通过以下函数throw\u with_nested\u wrapper
替换所有这些std::throw\u with_nested
。
众所周知,代码C++ >代码> >代码> >
因此,宏THROW\u 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)...));
};
最后,前三个函数f
、g
和h
被重写和简化如下:
[[noreturn]] void f(){
THROW_WITH_NESTED(SomeException, "SomeException, fundamental level.");
}
void g(){
HOOK(f());
};
void h(){
HOOK(g());
}
提取错误信息
提取嵌套异常的所有解释性信息是一项简单的任务。
将最外层的try-catch块捕获的异常传递到下面的函数output\u exceptions\u impl
,我们就可以做到这一点。
每个嵌套异常都可以由递归抛出。
由于此成员函数在没有存储的异常时调用std::terminate
,因此我们应该应用dynamic\u cast
来避免它,正如本文中所指出的:
然后,从h
抛出的所有异常都可以通过以下代码进行跟踪和打印。
在代码的右行插入嵌套的宏THROW\u
、HOOK
、CATCH\u BEGIN
和CATCH\u END
,我们可以找到每个线程中的异常:
CATCH_BEGIN // most outer try-catch block in each thread
...
HOOK(h());
...
CATCH_END(std::cout)
然后我们得到以下输出,其中文件名和行号只是一个示例。
所有可用信息都会记录下来:
运行时错误:prog.cc,l.119,h(),上层
运行时错误:prog.cc,l.113,g(),上层
运行时错误:prog.cc,l.109,f(),上层
逻辑错误:prog.cc,l.105,某些异常,基本级别
2.如果是fooresource
第一个要求是
将一些可选的上下文信息作为额外参数传递给Ctor,然后将其存储在异常中
让我们定义以下特殊异常类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
添加到fooResourcece::fooResourcece
并将throw
替换为throw\u和_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的函数
使用HOOK
创建每个FooResource
,我们可以获得关于在上层中ctor失败的位置的信息。
调用方将如下所示。
通过这种方式,所有错误信息(包括消息、上下文及其位置)都将在每个线程中得到澄清
CATCH_BEGIN // most outer try-catch block in each thread
...
auto resource = HOOK(FooResouce(T(), "context"));
...
CATCH_END(std::cout)
最后,
在callsite中使用pushContext函数。此函数将使用线程本地存储来存储上下文
虽然我不知道这个需求的细节,但是由于我们可以在output\u exceptions\u impl
中调用SomeException::getContext
,如下所示,并从每个抛出的SomethingExceptions
中获取所有上下文,我认为我们也可以这样存储它们:
void output\u exceptions\u impl(
常量标准::异常和异常,
标准::ostream&stream,
bool isFirstCall=false)
{
...
catch(const SomeException&e){//添加此部分。
流动
可能上下文的\uuuu文件和\uuuuu行和\uuuuuu行宏?@TrebuchetMS文件和行当前不在我感兴趣的上下文中,可能是std::nested\u exception
?顺便说一句,\uuuuu行和调试器有什么问题?@Hiroki std::nested\u exception与(3)相同,对吗?调试器?不能创建资源不是一个bug。@user877329仅仅因为它不是bug并不意味着禁止您启动调试器并使用它跟踪
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;
}
};
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);
}
...
}
...
};
CATCH_BEGIN // most outer try-catch block in each thread
...
auto resource = HOOK(FooResouce(T(), "context"));
...
CATCH_END(std::cout)
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);
}
...
}