2016-05-16 18 views
10

Giới thiệu:C++ suy ra kiểu của một ngoại lệ lồng nhau

đưa ra:

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

Khi chúng ta gọi là std::throw_with_nested(X("foo")), những gì đang thực sự ném không phải là một X. Đó là một số loại có nguồn gốc từ cả hai số Xstd::nested_exception.

do đó, sự khẳng định sau sẽ thất bại:

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

Những gì tôi muốn làm là suy luận rằng hai trường hợp ngoại lệ này có liên quan.

nỗ lực đầu tiên:

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

này sai vì std::nested_exception::nested_ptr() trả về một con trỏ đến ngoại trừ tiếp theo xuống dòng, không phải là giao diện X của ngoại lệ hiện hành.

Tôi đang tìm ý tưởng và giải pháp (di động) cho phép tôi khôi phục typeid (X) từ 'ngoại lệ với tên không xác định' được thư viện chuẩn ném trong thời gian std::rethrow_exception.

C++ 14 và C++ 1z là tốt.

Tại sao ?:

Bởi vì tôi muốn để có thể unwrap một hệ thống phân cấp ngoại lệ hoàn thành và chuyển nó qua một phiên rpc, hoàn chỉnh với tên loại ngoại lệ.

Tôi lý tưởng không muốn phải viết một khối catch có tất cả các loại ngoại lệ trong hệ thống, mà sẽ phải được đặt hàng yếu bởi độ sâu dẫn xuất.

Một ví dụ khác của chức năng dự kiến ​​(và minh họa về lý do tại sao cách tiếp cận của tôi không hoạt động):

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 ghi nhận, cảm ơn. Như bạn có thể thấy, tôi đang sử dụng type_index trong mã. Tôi sẽ cập nhật câu hỏi để so sánh a và b theo tên(). –

+0

Lưu ý chắc chắn những gì bạn muốn chứng minh với ví dụ cuối cùng của bạn, bạn có 'std :: runtime_error' vs' X' ... – Jarod42

+0

@ Jarod42 đúng. X là gói một runtime_error lồng nhau. Tôi muốn suy ra loại X (trình bao bọc) từ loại thực của nó. –

Trả lời

1

Một cách để khắc là sử dụng liên tục throw_with_nested của riêng bạn, trong đó bạn tiêm chức năng bạn muốn:

#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

Bất cứ lúc nào bạn muốn nhiều chức năng hơn, bạn có thể tinh chỉnh identifiable_baseidentifiable_exception để hỗ trợ những gì bạn muốn.

+0

Vâng, đó là khá nhiều kết luận tôi cũng đã đến. –

+0

Sau khi xem xét lại điều này, tôi quyết định rằng nó quá nhiều gánh nặng cho người dùng mã của tôi để buộc họ vào một cơ chế ném phi tiêu chuẩn. Tôi quyết định demangling typename ngoại lệ và giải nén các loại cơ sở với một regex. Thoạt nhìn nó có vẻ chậm, nhưng nó chỉ xảy ra tại thời điểm mở bỏ ngoại lệ vì vậy nó sẽ không thường xuyên. Đã cung cấp câu trả lời. –

3

Phỏng print_exception từ http://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

Lưu ý khá. 'rethrow_if_nested()' ném ngoại lệ bên trong nếu có. Do đó, hàm này suy ra loại ngoại lệ không có tổ chức sâu nhất trong chuỗi ngoại lệ lồng nhau, không phải kiểu cơ bản của e. –

+0

cùng một vấn đề theo nỗ lực của tôi: "Điều này không thành công vì std :: nested_exception :: nested_ptr() trả về một con trỏ tới ngoại lệ tiếp theo xuống dòng, chứ không phải giao diện X của ngoại lệ hiện tại." –

+0

@RichardHodges: Bạn có thể đưa ra ví dụ không thành công? Ví dụ của bạn, cả hai ngoại lệ đều có deduce_exception_type của 'X'. – Jarod42

0

Nhờ những người đã trả lời.

Cuối cùng, tôi cảm thấy cách đáng tin cậy nhất là phá hỏng kết quả của typeid::name() và xóa bất kỳ phần "lồng nhau" nào của tên tệp.

Tôi đã tạo một bản đồ đăng ký ngoại lệ, nhưng điều này sau đó yêu cầu cơ chế ném và lấy lại không chuẩn để móc vào bản đồ.

Đó là một chút nền tảng cụ thể nhưng nó có thể được gói gọn trong một hàm thư viện:

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

trường hợp sử dụng của tôi (Exception có nguồn gốc từ 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"); 
    } 
} 

nơi demangle() một lần nữa được xác định về mã nền tảng cụ thể. Trong trường hợp của tôi:

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

đâu demangled_string là một wrapper thuận tiện xung quanh bộ nhớ trở về từ abi::__cxa_demangle (hoặc tương tự trong các cửa sổ):

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()); 
} 
Các vấn đề liên quan