我有一个管理资源的课程。它需要一个可以从路径中检索资源的Loader类。 Loader是一个抽象基类,所以任何人都可以创建一个新的加载器。
如果请求的资源不在缓存中,资源管理器将从加载器请求它。如果加载程序失败,资源管理器会抛出一个名为LoadingError的异常类。
我希望Loader类在失败时抛出异常。如果需要,我可以要求他们从我自己的异常基类继承。
问题是,当抛出LoadingError时,我希望它包含有关失败原因的具体信息。但我不确切知道Loader会抛出什么异常。 (用户可能会在资源管理器周围的catch块中)。
要么我只能抛出一些一般信息的LoadingError,要么允许来自Loader的特定异常出来而不要触摸它。但我想捕获异常并将其捆绑在我自己的LoadingError异常中,或者在LoadingError中指向它。但我不知道指向异常将持续多长时间(关注坏指针)。
我应该做哪三个? (以及如何,对于第三个......)
由于
答案 0 :(得分:8)
嵌套异常怎么样?
try { /* ... */ }
catch (...)
{
throw MyException("An error occurred", std::current_exception());
}
只需创建一个存储异常的合适类:
struct MyException : std::exception
{
std::string message;
std::exception_ptr nested_exception;
MyException(std::string m, std::exception_ptr e)
: message(std::move(m))
, nested_exception(std::move(e))
{ }
// ...
};
捕获异常时,捕获器可以重新抛出嵌套异常:
try { /* load resource */ }
catch (MyException & e)
{
log("Resource loading failed: " + e.what());
std::rethrow_exception(e.nested_exception);
}
事实上,整个逻辑已由标准库通过std::throw_with_nested
提供。
答案 1 :(得分:6)
当可以在整个代码中强制使用自己的异常类时,执行嵌套异常的最简单方法无疑是定义包含 {{1}的自定义异常类,可以通过 std::exception_ptr
获取, std::current_exception
标题。
但是,C ++ 11通过 <exception>
类以及 std::nested_exception
等函数支持嵌套异常。 std::throw_with_nested
构造函数会选择? current_exception()`,如果有的话,并将其存储为嵌套异常。无法用expclitly指定嵌套异常:它始终是当前异常。
此机制还支持通过非异常感知代码(例如通过C回调)进行异常传播,并且它支持任意类的异常(和嵌套异常),而不仅仅是nested_exception
和派生类。
所以,例如库代码使用此功能,然后希望能够处理此类标准嵌套异常,而不仅仅是一个自己的自定义(更简单易用但不太通用)方案。
可悲的是,正如我正在写这篇文章(2014年1月),Visual C ++编译器还不支持std::exception
,尽管它确实支持其余大部分机器。令人高兴的是,定义这些东西并不困难。例如,谷歌搜索,我在Tomaka-17's blog找到了工作代码,这是我在下面修改的代码 - std::nested_exception
支持Visual C ++ :
std::nested_exception
使用g ++ 4.7.2,定义这个东西会更加复杂,但是由于g ++ 4.8.2已经有了它,所以没有必要:对于g ++,只需在必要时升级编译器。
然后下一个问题是如何检索嵌套异常信息。
基本上归结为迭代重新抛出并捕获每个嵌套异常,例如:如下:
#include <exception> // std::rethrow_exception
// For Visual C++ define CPPX_NORETURN as "__declspec(noreturn)"
#ifndef CPPX_NORETURN
# define CPPX_NORETURN [[noreturn]]
#endif
// Visual C++ 12.0 lacks these things.
// Code adapted from http://blog.tomaka17.com/2013/07/c11-nested-exceptions/.
#if !defined( GOOD_COMPILER )
#include <utility> // std::forward
#include <type_traits> // std::remove_reference
namespace std {
class nested_exception
{
private:
exception_ptr nested;
public:
CPPX_NORETURN
void rethrow_nested() const
{ rethrow_exception(nested); }
exception_ptr nested_ptr() const { return nested; }
virtual ~nested_exception() {}
nested_exception() : nested( current_exception() ) {}
};
template< class Type >
CPPX_NORETURN
void throw_with_nested( Type&& t )
{
typedef remove_reference<Type>::type Pure_type;
struct Unspecified_mi_type
: nested_exception
, Pure_type
{
Unspecified_mi_type( Type&& t )
: Pure_type( forward<Type>( t ) )
{}
};
if (is_base_of<nested_exception, Pure_type>::value)
{
throw forward<Type>( t );
}
else
{
throw Unspecified_mi_type( forward<Type>( t ) );
}
}
template< class X >
void rethrow_if_nested( X const& x )
{
if( auto const ptr = dynamic_cast< nested_exception const* >( &x ) )
{
ptr->rethrow_nested(); // It's specified to do this, C++11 §18.8/8.
}
}
}
#endif // not GOOD_COMPILER
输出:
!Resource_manager::foo failed !<because> Loading failed
免责声明:我为此答案编写了上述代码,因此未经过广泛测试。但无论如何,享受!