2016-05-16 15 views
10

Introduzione:C++ dedurre il tipo di un'eccezione annidata

dato:

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

Quando chiamiamo std::throw_with_nested(X("foo")), ciò che è effettivamente gettato non è un X. È un tipo derivato sia da X sia da std::nested_exception.

quindi, la seguente asserzione avrà esito negativo:

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

Quello che vorrei fare è dedurre che queste due eccezioni riguardano.

Primo tentativo:

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

Questo fallisce perché std::nested_exception::nested_ptr() restituisce un puntatore alla prossima eccezione verso il basso la linea, non l'interfaccia X dell'eccezione corrente.

Sto cercando idee e soluzioni (portatili) che mi consentano di recuperare il tipoid (X) dall''eccezione con nome sconosciuto 'generata dalla libreria standard durante std::rethrow_exception.

C++ 14 e C++ 1z vanno bene.

Perché ?:

Perché voglio essere in grado di scartare una gerarchia un'eccezione completo e trasmetterlo attraverso una sessione RPC, con nomi tipo di eccezione.

Preferibilmente non voglio dover scrivere un blocco catch con ogni tipo di eccezione nel sistema, che dovrebbe essere debolmente ordinato per profondità di derivazione.

Un ulteriore esempio di funzionalità prevista (e un'illustrazione del motivo per cui il mio approccio non funziona):

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 notato, grazie. Come puoi vedere, sto usando type_index nel codice. Aggiornerò la domanda per confrontare aeb per nome(). –

+0

Nota che cosa vuoi dimostrare con il tuo ultimo esempio, hai 'std :: runtime_error' vs' X' ... – Jarod42

+0

@ Jarod42 giusto. X sta eseguendo il wrapping di un runtime_error nidificato. Voglio dedurre il tipo di X (il wrapper) dal suo tipo reale non nominato. –

risposta

1

Un modo per aggirare è quello di utilizzare costantemente il proprio throw_with_nested, in cui si inserisce la funzionalità desiderata:

#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

Ogni volta che si desiderano più funzionalità, è possibile modificare identifiable_base e identifiable_exception per sostenere ciò che si desidera.

+0

Sì, questa è praticamente la conclusione che ho raggiunto anche io. –

+0

Dopo aver ripensato a questo ho deciso che è troppo oneroso per gli utenti del mio codice costringerli a un meccanismo di lancio non standard. Ho deciso di decodificare l'eccezione typename e di estrarre il tipo base con un'espressione regolare. A prima vista sembra lento, ma accade solo al punto di scartare l'eccezione, quindi non sarà frequente. Risposta fornita –

3

Adattato print_exception da 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

Nota abbastanza. 'rethrow_if_nested()' lancia l'eccezione interna se presente. Questa funzione deduce quindi il tipo dell'eccezione più profonda non titolare della nidificazione nella catena delle eccezioni nidificata, non il tipo sottostante di e. –

+0

stesso problema del mio tentativo: "Questo fallisce perché std :: nested_exception :: nested_ptr() restituisce un puntatore all'eccezione successiva lungo la linea, non all'interfaccia X dell'eccezione corrente." –

+0

@RichardHodges: puoi dare un esempio che fallisce? Per il tuo esempio, entrambe le eccezioni hanno deduce_exception_type di 'X'. – Jarod42

0

Grazie ai ragazzi che hanno risposto.

Alla fine ho sentito che il modo più affidabile era quello di svelare il risultato di typeid::name() e rimuovere qualsiasi parte "annidata" del nome del tipo.

Ho dovuto creare una mappa di registrazione delle eccezioni, ma questo ha richiesto meccanismi di lancio e rethrow non standard da agganciare alla mappa.

E 'un po' piattaforma specifica, ma può essere incapsulato in una funzione di libreria:

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

mio caso d'uso (Exception deriva da 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"); 
    } 
} 

dove demangle() nuovo è definito in termini di codice specifico per piattaforma. Nel mio caso:

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

Dove demangled_string è una comoda involucro intorno alla memoria tornato da abi::__cxa_demangle (o simile in 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()); 
} 
Problemi correlati