C ++推断嵌套异常的类型
c++ deduce the type of a nested exception
简介:
给定:
struct X : std::runtime_error {
using std::runtime_error::runtime_error;
};
当我们调用std::throw_with_nested(X("foo"))
时,实际抛出的不是X
。它是从 X
和 std::nested_exception
.
派生的某种类型
因此,以下断言将失败:
const std::type_info *a = nullptr, *b = nullptr;
try
{
throw X("1");
}
catch(X& x) {
a = std::addressof(typeid(x));
try {
std::throw_with_nested(X("2"));
}
catch(X& x) {
b = std::addressof(typeid(x));
}
}
assert(std::string(a->name()) == std::string(b->name()));
我想做的是推断这两个异常是相关的。
第一次尝试:
std::type_index
deduce_exception_type(const std::exception* pe)
{
if (auto pnested = dynamic_cast<const std::nested_exception*>(pe))
{
try {
std::rethrow_exception(pnested->nested_ptr());
}
catch(const std::exception& e)
{
return deduce_exception_type(std::addressof(e));
}
}
else {
return typeid(*pe);
}
}
失败是因为 std::nested_exception::nested_ptr()
returns 指向下一个异常的指针,而不是当前异常的 X
接口。
我正在寻找(可移植的)想法和解决方案,使我能够从 std::rethrow_exception
期间标准库抛出的 'exception with unknown name' 中恢复 typeid(X)。
c++14 和 c++1z 都可以。
为什么?:
因为我希望能够解开完整的异常层次结构并通过 rpc 会话传输它,完成异常类型名称。
理想情况下,我不想编写一个包含系统中所有异常类型的 catch 块,这必须按派生深度进行弱排序。
预期功能的进一步示例(以及为什么我的方法不起作用的说明):
const std::type_info *b = nullptr;
try
{
throw std::runtime_error("1");
}
catch(std::exception&) {
try {
std::throw_with_nested(X("2"));
}
catch(X& x) {
// PROBLEM HERE <<== X& catches a std::_1::__nested<X>, which
// is derived from X and std::nested_exception
b = std::addressof(typeid(x));
}
}
assert(std::string(typeid(X).name()) == std::string(b->name()));
改编自 http://en.cppreference.com/w/cpp/error/nested_exception 的 print_exception
:
const std::type_info&
deduce_exception_type(const std::exception& e)
{
try {
std::rethrow_if_nested(e);
} catch(const std::exception& inner_e) {
return deduce_exception_type(inner_e);
} catch(...) {
}
return typeid(e);
}
一种解决方法是始终使用您自己的 throw_with_nested
,在其中注入您想要的功能:
#include <typeinfo>
#include <exception>
struct identifiable_base {
virtual std::type_info const& type_info() const = 0;
};
template<typename Exception>
struct identifiable_exception: Exception, identifiable_base {
using Exception::Exception;
explicit identifiable_exception(Exception base)
: Exception(std::move(base))
{}
std::type_info const& type_info() const override
{
// N.B.: this is a static use of typeid
return typeid(Exception);
}
};
template<typename Exception>
identifiable_exception<std::decay_t<Exception>> make_identifiable_exception(Exception&& exception)
{ return identifiable_exception<std::decay_t<Exception>> { std::forward<Exception>(exception) }; }
// N.B.: declared with a different name than std::throw_with_nested to avoid ADL mistakes
template<typename Exception>
[[noreturn]] void throw_with_nested_identifiable(Exception&& exception)
{
std::throw_with_nested(make_identifiable_exception(std::forward<Exception>(exception)));
}
任何时候你想要更多的功能,你可以调整 identifiable_base
和 identifiable_exception
来支持你想要的。
感谢回复的人。
最后我觉得最可靠的方法是分解 typeid::name()
的结果并删除类型名称的任何 "nested" 部分。
我确实擅长构建异常注册映射,但这需要非标准的抛出和重新抛出机制才能连接到映射中。
它有点特定于平台,但可以封装在库函数中:
#include <regex>
#include <string>
namespace
{
std::string remove_nested(std::string demangled)
{
#if _LIBCPP_VERSION
static const std::regex re("^std::__nested<(.*)>$");
#elif __GLIBCXX__
static const std::regex re("^std::_Nested_exception<(.*)>$");
#endif
std::smatch match;
if (std::regex_match(demangled, match, re))
{
demangled = match[1].str();
}
return demangled;
}
}
我的用例(Exception
源自 google::protobuf::Message
):
void populate(Exception& emsg, const std::exception& e)
{
emsg.set_what(e.what());
emsg.set_name(remove_nested(demangle(typeid(e))));
try {
std::rethrow_if_nested(e);
}
catch(std::exception& e)
{
auto pnext = emsg.mutable_nested();
populate(*pnext, e);
}
catch(...) {
auto pnext = emsg.mutable_nested();
pnext->set_what("unknown error");
pnext->set_name("unknown");
}
}
其中 demangle()
再次根据特定于平台的代码定义。就我而言:
demangled_string demangle(const char* name)
{
using namespace std::string_literals;
int status = -4;
demangled_string::ptr_type ptr {
abi::__cxa_demangle(name, nullptr, nullptr, &status),
std::free
};
if (status == 0) return { std::move(ptr) };
switch(status)
{
case -1: throw std::bad_alloc();
case -2: {
std::string msg = "invalid mangled name~";
msg += name;
auto p = (char*)std::malloc(msg.length() + 1);
strcpy(p, msg.c_str());
return demangled_string::ptr_type { p, std::free };
}
case -3:
assert(!"invalid argument sent to __cxa_demangle");
throw std::logic_error("invalid argument sent to __cxa_demangle");
default:
assert(!"PANIC! unexpected return value");
throw std::logic_error("PANIC! unexpected return value");
}
}
demangled_string demangle(const std::type_info& type)
{
return demangle(type.name());
}
其中 demangled_string
是对从 abi::__cxa_demangle
(或 windows 中的类似内容)返回的内存的方便包装:
struct demangled_string
{
using ptr_type = std::unique_ptr<char, void(*)(void*)>;
demangled_string(ptr_type&& ptr) noexcept;
const char* c_str() const;
operator std::string() const;
std::ostream& write(std::ostream& os) const;
private:
ptr_type _ptr;
};
demangled_string::demangled_string(ptr_type&& ptr) noexcept
: _ptr(std::move(ptr))
{}
std::ostream& demangled_string::write(std::ostream& os) const
{
if (_ptr) {
return os << _ptr.get();
}
else {
return os << "{nullptr}";
}
}
const char* demangled_string::c_str() const
{
if (!_ptr)
{
throw std::logic_error("demangled_string - zombie object");
}
else {
return _ptr.get();
}
}
demangled_string::operator std::string() const {
return std::string(c_str());
}
简介:
给定:
struct X : std::runtime_error {
using std::runtime_error::runtime_error;
};
当我们调用std::throw_with_nested(X("foo"))
时,实际抛出的不是X
。它是从 X
和 std::nested_exception
.
因此,以下断言将失败:
const std::type_info *a = nullptr, *b = nullptr;
try
{
throw X("1");
}
catch(X& x) {
a = std::addressof(typeid(x));
try {
std::throw_with_nested(X("2"));
}
catch(X& x) {
b = std::addressof(typeid(x));
}
}
assert(std::string(a->name()) == std::string(b->name()));
我想做的是推断这两个异常是相关的。
第一次尝试:
std::type_index
deduce_exception_type(const std::exception* pe)
{
if (auto pnested = dynamic_cast<const std::nested_exception*>(pe))
{
try {
std::rethrow_exception(pnested->nested_ptr());
}
catch(const std::exception& e)
{
return deduce_exception_type(std::addressof(e));
}
}
else {
return typeid(*pe);
}
}
失败是因为 std::nested_exception::nested_ptr()
returns 指向下一个异常的指针,而不是当前异常的 X
接口。
我正在寻找(可移植的)想法和解决方案,使我能够从 std::rethrow_exception
期间标准库抛出的 'exception with unknown name' 中恢复 typeid(X)。
c++14 和 c++1z 都可以。
为什么?:
因为我希望能够解开完整的异常层次结构并通过 rpc 会话传输它,完成异常类型名称。
理想情况下,我不想编写一个包含系统中所有异常类型的 catch 块,这必须按派生深度进行弱排序。
预期功能的进一步示例(以及为什么我的方法不起作用的说明):
const std::type_info *b = nullptr;
try
{
throw std::runtime_error("1");
}
catch(std::exception&) {
try {
std::throw_with_nested(X("2"));
}
catch(X& x) {
// PROBLEM HERE <<== X& catches a std::_1::__nested<X>, which
// is derived from X and std::nested_exception
b = std::addressof(typeid(x));
}
}
assert(std::string(typeid(X).name()) == std::string(b->name()));
改编自 http://en.cppreference.com/w/cpp/error/nested_exception 的 print_exception
:
const std::type_info&
deduce_exception_type(const std::exception& e)
{
try {
std::rethrow_if_nested(e);
} catch(const std::exception& inner_e) {
return deduce_exception_type(inner_e);
} catch(...) {
}
return typeid(e);
}
一种解决方法是始终使用您自己的 throw_with_nested
,在其中注入您想要的功能:
#include <typeinfo>
#include <exception>
struct identifiable_base {
virtual std::type_info const& type_info() const = 0;
};
template<typename Exception>
struct identifiable_exception: Exception, identifiable_base {
using Exception::Exception;
explicit identifiable_exception(Exception base)
: Exception(std::move(base))
{}
std::type_info const& type_info() const override
{
// N.B.: this is a static use of typeid
return typeid(Exception);
}
};
template<typename Exception>
identifiable_exception<std::decay_t<Exception>> make_identifiable_exception(Exception&& exception)
{ return identifiable_exception<std::decay_t<Exception>> { std::forward<Exception>(exception) }; }
// N.B.: declared with a different name than std::throw_with_nested to avoid ADL mistakes
template<typename Exception>
[[noreturn]] void throw_with_nested_identifiable(Exception&& exception)
{
std::throw_with_nested(make_identifiable_exception(std::forward<Exception>(exception)));
}
任何时候你想要更多的功能,你可以调整 identifiable_base
和 identifiable_exception
来支持你想要的。
感谢回复的人。
最后我觉得最可靠的方法是分解 typeid::name()
的结果并删除类型名称的任何 "nested" 部分。
我确实擅长构建异常注册映射,但这需要非标准的抛出和重新抛出机制才能连接到映射中。
它有点特定于平台,但可以封装在库函数中:
#include <regex>
#include <string>
namespace
{
std::string remove_nested(std::string demangled)
{
#if _LIBCPP_VERSION
static const std::regex re("^std::__nested<(.*)>$");
#elif __GLIBCXX__
static const std::regex re("^std::_Nested_exception<(.*)>$");
#endif
std::smatch match;
if (std::regex_match(demangled, match, re))
{
demangled = match[1].str();
}
return demangled;
}
}
我的用例(Exception
源自 google::protobuf::Message
):
void populate(Exception& emsg, const std::exception& e)
{
emsg.set_what(e.what());
emsg.set_name(remove_nested(demangle(typeid(e))));
try {
std::rethrow_if_nested(e);
}
catch(std::exception& e)
{
auto pnext = emsg.mutable_nested();
populate(*pnext, e);
}
catch(...) {
auto pnext = emsg.mutable_nested();
pnext->set_what("unknown error");
pnext->set_name("unknown");
}
}
其中 demangle()
再次根据特定于平台的代码定义。就我而言:
demangled_string demangle(const char* name)
{
using namespace std::string_literals;
int status = -4;
demangled_string::ptr_type ptr {
abi::__cxa_demangle(name, nullptr, nullptr, &status),
std::free
};
if (status == 0) return { std::move(ptr) };
switch(status)
{
case -1: throw std::bad_alloc();
case -2: {
std::string msg = "invalid mangled name~";
msg += name;
auto p = (char*)std::malloc(msg.length() + 1);
strcpy(p, msg.c_str());
return demangled_string::ptr_type { p, std::free };
}
case -3:
assert(!"invalid argument sent to __cxa_demangle");
throw std::logic_error("invalid argument sent to __cxa_demangle");
default:
assert(!"PANIC! unexpected return value");
throw std::logic_error("PANIC! unexpected return value");
}
}
demangled_string demangle(const std::type_info& type)
{
return demangle(type.name());
}
其中 demangled_string
是对从 abi::__cxa_demangle
(或 windows 中的类似内容)返回的内存的方便包装:
struct demangled_string
{
using ptr_type = std::unique_ptr<char, void(*)(void*)>;
demangled_string(ptr_type&& ptr) noexcept;
const char* c_str() const;
operator std::string() const;
std::ostream& write(std::ostream& os) const;
private:
ptr_type _ptr;
};
demangled_string::demangled_string(ptr_type&& ptr) noexcept
: _ptr(std::move(ptr))
{}
std::ostream& demangled_string::write(std::ostream& os) const
{
if (_ptr) {
return os << _ptr.get();
}
else {
return os << "{nullptr}";
}
}
const char* demangled_string::c_str() const
{
if (!_ptr)
{
throw std::logic_error("demangled_string - zombie object");
}
else {
return _ptr.get();
}
}
demangled_string::operator std::string() const {
return std::string(c_str());
}