2016-05-16 89 views
10

介紹的類型:給定C++演繹出嵌套異常

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())); 
+0

@ Jarod42指出,謝謝。正如你所看到的,我在代碼中使用type_index。我將更新問題以按名稱()比較a和b。 –

+0

注意你最後想要展示的東西,你有'std :: runtime_error' vs'X' ... – Jarod42

+0

@ Jarod42沒錯。 X正在封裝一個嵌套的runtime_error。我想從它的未命名的實際類型中推導出X(包裝器)的類型。 –

回答

1

圍繞一個途徑是一貫地使用你自己的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支持你想要的東西。

+0

是的,這也是我得出的結論。 –

+0

重新思考這個之後,我決定對我的代碼的用戶負擔太多,迫使他們進入非標準的拋出機制。我決定去除異常類型名稱並用正則表達式提取基類型。乍一看,它看起來很慢,但它只發生在解開異常的時候,所以它很少出現。提供答覆。 –

3

改編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

+0

挺好的。 'rethrow_if_nested()'拋出內部異常(如果存在)。因此,該函數推斷嵌套異常鏈中最深的非嵌套擁有者異常的類型,而不是e的基礎類型。 –

+0

與我的嘗試相同的問題:「由於std :: nested_exception :: nested_ptr()返回一個指向下一個異常的指針,而不是當前異常的X接口。 –

+0

@RichardHodges:你能舉出失敗的例子嗎?對於你的例子,這兩個異常都有'X'的deduce_exception_type。 – Jarod42

0

感謝那些迴應的傢伙。

最後我覺得最可靠的方法是去掉typeid::name()的結果,並刪除typename的任何「嵌套」部分。

我的確建立了一個異常註冊地圖,但是這需要非標準的拋出和重新拋出機制來掛鉤到地圖中。

這是一個小平臺,具體的,但它可以在一個庫函數封裝:

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

我的使用情況(Exceptiongoogle::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()); 
}