2015-01-07 1 views
0

У нас есть базовая ситуация, которая выглядит, как выглядит следующим образом:Укажите дополнительный базовый тип в иерархии исключений на C++?

// 3rd party lib: 

namespace ns3p { 
    class OperationException : public std::exception; // yes, no `virtual` here 
} 

// our code: 

// wrapper with additional information to the ns3p::OperationException 
class ModuleOpException : public ns3p::OperationException; 

// our catch sites: 
// mostly: 
catch (ModuleOpException const& ex) { ... 
// but sometimes: 
catch (ns3p::OperationException const& ex) { ... 

Теперь этот вырос, чтобы включить дополнительные исключения, все полученные из ModuleOpException, что не имеют ничего общего с какими-либо ошибками от 3 участника lib, но их просто бросают в том же контексте, где используются материалы из сторонней библиотеки lib.

// This indirectly derives from ns3p::OperationException, even though 
// there is *no* underlying OperationException at all. It just thrown "stand alone" 
// and caught by ModuleOpException& : 
class InvalidXYZException : public ModuleOpExcpetion; 

Теперь мы думали о «переворачивания» иерархии, чтобы лучше отражать реальную ситуацию и сделать это с (первоначально) минимальным воздействием на другой код.

Мы планируем сделать это:

// new base exception type: 
class ModuleBaseException : public virtual std::exception; 

// changed to derive both from our base type as well as the 3rd party type: 
class ModuleOpException : public virtual ModuleBaseException, public virtual ns3p::OperationException; 

// only derives from the base type: 
class InvalidXYZException : public virtual ModuleBaseException; 

// all catch sites change the catch of `ModuleOpException` to: 
catch (ModuleBaseException const& ex) { ... 
// and leave the catch ns3p::OperationException alone 

Это должно работать (? Надо это), за исключением того, что я не знаю, сколько не-виртуальное наследование от std::exception 3-й части типа исключения может испортить вещи. I думаю мы безопасны as long as noone tries to catch(std::exception const&) in which case the catch would fail для привязки во время выполнения, потому что преобразование неоднозначно.

Это похоже на жизнеспособное решение? Или это «действительно плохая идея», чтобы попытаться интегрировать тип non-virtual-std::exception с иерархией, как указано выше?

Примечание: Существует нулевой шанс (как в 0.00%), что мы можем когда-либо изменить 3-й партии LIB, чтобы получить «правильно» с virtual от станд :: исключением.


  • Конечно, если какой-либо catch(ns3p::OperationException&) в предыдущей версии «случайно» перехвачена InvalidXYZExecption это сломается сейчас, но это было бы приемлемо.
+0

Зачем вам так много классов исключений и зачем вам ModuleOpException вывести из класса исключений из сторонней библиотеки? – CashCow

ответ

1

Попытка ... Уловка должна произойти в месте, где исключение будет надлежащим образом обработано, или если исключение будет изменено на этом этапе и будет повторно показано.

Целью использования различных классов исключений является обращение с ними по-другому. (Мое мнение, что это плохая идея, включение какого-то кода было бы лучше, и вы, скорее всего, измените свое действие на основе типа только в редких случаях, например, повторите запрос, если ошибка будет временной).

В любом случае, сторонняя библиотека, я полагаю, используется для реализации собственных библиотек. Пользователи ваших библиотек не хотят знать об исключениях из библиотеки, которую вы используете для реализации.

Поэтому вы должны отвлечь это от пользователя своих библиотек, поймав любой из них и перебросив их как свои собственные, так, как ожидал пользователь.

Исключения из вашей библиотеки являются такой же частью «контракта», что и возвращаемые значения из ваших функций.

Таким образом, пользователи ожидали потенциального обнаружения исключения ModuleBaseException или исключения ModuleOpException, но не исключения ns3p :: OperationException.

Ваш собственный код должен поймать их, возможно, преобразовать их в свой тип ModuleOpException и вместо этого выбросить.

+0

Итак, по существу, вы говорите: «Не выходите из стороннего типа»? –

+0

В этом случае я не могу понять, что вы получаете, используя это. Вы используете стороннюю библиотеку, они не используют ваш код и – CashCow

+0

Думая об этом, * не * получая от третьей стороны вообще, возможно * это лучшее решение. Спасибо. –

1

ИМХО это должно сработать. C++ спецификация говорит:

Обработчик совпадение для объекта исключения типа Е, если ... обработчик имеет тип T сорте или сорта T & и T является однозначным общественности базового класса Е или ...

и Обработчики для блока try стараются в порядке появления.

Так до тех пор, как вы ловите ModuleBaseException, прежде чем ns3p::OperationException любое исключение, вытекающие из ModuleBaseException должны быть пойманы в правильном обработчиком, даже если он также является производным от ns3p::OperationException.

Единственная проблема заключается в том, что все методы и поля из std :: exception будут дублироваться в ваших классах исключений, и вы всегда должны квалифицировать их как исходящие из исключения ModuleBaseException, чтобы избежать двусмысленности. Вероятно, вы должны использовать общую утилиту для обработки исключения, которое будет использоваться во всех обработчиках.

+0

На уровне синтаксиса или что бы это ни было но в этом случае я думаю, что пользователь немного запутывается в иерархии и что заставляет какое исключение – CashCow

1

При создании существующей иерархии исключений у вас есть множественное наследование (одно для вашего и одно для существующей иерархии). Во избежание недоразумений можно передать и сохранить указатель на существующую иерархию:

#include <stdexcept> 
#include <sstream> 

// Error 
//============================================================================= 

/// Base class for all exceptions in a library. 
/// \NOTE The constructor requires a std::exception as argument 
class Error 
{ 
    // Construction 
    // ============ 

    public: 
    virtual ~Error() noexcept {}; 

    protected: 
    Error(std::exception& exception) 
    : m_exception(&exception) 
    {} 


    // Element Access 
    // ============== 

    public: 
    const char* msg() const { return m_exception->what(); } 

    // Cast 
    // ==== 

    protected: 
    template <typename Derived> 
    static std::exception& cast(Derived& derived) { 
     return static_cast<std::exception&>(derived); 
    } 

    private: 
    std::exception* m_exception; 
}; 


// Runtime Errors 
// ============================================================================ 

/// A Runtime Error. 
class ErrorRuntime : public std::runtime_error, public Error 
{ 
    public: 
    explicit ErrorRuntime(const std::string& msg) 
    : std::runtime_error(msg), Error(cast(*this)) 
    {} 
}; 

// Test 
// ==== 

#include <iostream> 

int main() 
{ 
    try { 
     throw ErrorRuntime("Hello Exception"); 
    } 
    catch(const std::exception& e) { 
     try { 
      std::cerr << "std::exception: " << e.what() << "\n"; 
      throw; 
     } 
     catch(const Error& e) { 
      try { 
       // No what here 
       std::cerr << "Error: " << e.msg() << "\n"; 
       throw; 
      } 
      catch(const ErrorRuntime& e) { 
       std::cerr << "ErrorRuntime: " << e.what() << "\n"; 
      } 
     } 
    } 
} 

Однако, если у вас есть библиотека третьей стороны, вы можете скрыть все исключения из этой библиотеки (как уже упоминалось другими). Затраты на скрытие составляют два уровня для входящих и исходящих исключений.