【问题标题】:c++ deduce the type of a nested exceptionc++ 推断嵌套异常的类型
【发布时间】:2016-05-16 14:30:19
【问题描述】:

简介:

给定:

struct X : std::runtime_error {
  using std::runtime_error::runtime_error;
};

当我们调用std::throw_with_nested(X("foo")) 时,实际抛出的不是X。它是从Xstd::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() 返回指向下一个异常的指针,而不是当前异常的X 接口。

我正在寻找(便携式)想法和解决方案,使我能够从std::rethrow_exception 期间标准库抛出的“未知名称的异常”中恢复 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()));

【问题讨论】:

  • @Jarod42 指出,谢谢。如您所见,我在代码中使用了 type_index。我将更新问题以按名称()比较 a 和 b。
  • 注意你想用最后一个例子演示什么,你有std::runtime_error vs X...
  • @Jarod42 没错。 X 包装了一个嵌套的 runtime_error。我想从未命名的真实类型中推断出 X(包装器)的类型。
  • @Jarod42 好的,很抱歉。我已经修正了错字。

标签: c++ c++11 exception c++14 c++17


【解决方案1】:

改编自print_exceptionhttp://en.cppreference.com/w/cpp/error/nested_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);
}

Demo

【讨论】:

  • 请注意。 rethrow_if_nested() 抛出内部异常(如果存在)。因此,这个函数是在推断嵌套异常链中最深的非嵌套异常的类型,而不是 e 的底层类型。
  • 与我的尝试相同的问题:“这失败了,因为 std::nested_exception::nested_ptr() 返回指向下一个异常的指针,而不是当前异常的 X 接口。”
  • @RichardHodges:你能举个失败的例子吗?对于您的示例,两个异常的 deuce_exception_type 均为 X
  • 添加了一个演示问题的测试用例。我们的方法产生一个 std::runtime_error 因为这是最深的(非容器)嵌套异常。
【解决方案2】:

一种解决方法是始终使用您自己的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)));
}

Live On Coliru

您可以随时调整identifiable_baseidentifiable_exception 以支持您想要的功能。

【讨论】:

  • 是的,这也是我得出的结论。
  • 在重新考虑之后,我决定强制我的代码用户使用非标准的抛出机制对他们来说负担太大了。我决定解构异常类型名并使用正则表达式提取基本类型。乍一看它似乎很慢,但它只发生在展开异常的时候,所以它很少发生。已提供答案。
【解决方案3】:

感谢回复的人。

最后,我觉得最可靠的方法是对typeid::name() 的结果进行分解,并删除类型名的任何“嵌套”部分。

我确实在构建异常注册映射方面有所收获,但这需要非标准的 throw 和 rethrow 机制来挂钩到映射中。

它有点特定于平台,但可以封装在库函数中:

#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());
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-26
    • 2013-03-19
    • 2011-05-11
    • 1970-01-01
    • 2022-12-07
    • 1970-01-01
    相关资源
    最近更新 更多