2009-10-22 3 views
17

Я пытался создать некоторые настраиваемые классы исключений для библиотеки C++, над которой я работаю. Эти пользовательские исключения захватывают дополнительную информацию, такую ​​как файл, номер строки и т. Д., Необходимые для отладки, если по какой-то причине при тестировании исключения не попадают в нужное место. Однако большинство людей, похоже, рекомендуют наследовать из класса std :: exception в STL, с которым я согласен, но мне было интересно, возможно, было бы лучше использовать множественное наследование для наследования от каждого из derived std::exception classes (например, std :: runtime_error) и настраиваемый класс исключений, как в приведенном ниже коде?Пользовательские исключения в C++

Другое дело, как работать с конструкторами копирования и операторами присваивания в классах исключений? Должны ли они быть отключены?

class Exception{ 
    public: 
     explicit Exception(const char *origin, const char *file, 
          const int line, const char *reason="", 
          const int errno=0) throw(); 

     virtual ~Exception() throw(); 

     virtual const char* PrintException(void) throw(); 

     virtual int GetErrno(void); 

    protected: 
     std::string m_origin; 
     std::string m_file; 
     int m_line; 
     std::string m_reason; 
     int m_errno; 
}; 

class RuntimeError: public virtual std::runtime_error, public Exception{ 
    public: 
       explicit RuntimeError(const char *origin, const char *file, 
            const int line, const char *reason="", 
            const int errno=0) throw(); 
     virtual ~RuntimeError() throw(); 
}; 
+0

Что бы вы получили от этого? Почему бы не использовать одиночное наследование? Зачем вам нужно настраивать * оба исключения Exception и RuntimeError? – jalf

+0

Я хотел использовать функциональность из своего пользовательского класса Exception и поддерживать иерархию исключений STL, наследуя от различных производных классов std :: exception. Таким образом, улов с: try { throw RunTimeException (...); } catch (std :: runtime_error & e) { ... } catch (std :: exception & e) {...} также поймает мой пользовательский класс. Это может быть пустая трата времени. Я мог бы также просто try { throw RunTimeException (...); } catch (std :: exception & e) {...} , но это может затруднить идентификацию исключения. Я не знаю, какова типичная практика, когда пользовательские классы исключений? – Tom

+0

Я бы сказал, просто наследуем от std :: runtime_error (или std :: exception). Если вам требуется конкретное лечение, у вас может быть несколько уловов в конце концов. –

ответ

11

Вы должны попробовать boost::exception

Целью повышающего Exception является облегчить конструкцию класса исключения иерархий и помочь написать обработки исключений и отчеты об ошибках кода.

Он поддерживает транспортировку произвольных данных на сайт улова, который иначе сложно из-за требований нет бросков (15.5.1) для исключения типов. Данные могут быть добавлены в любой объект исключения , либо непосредственно в выражение throw (15.1), либо в позднее, когда объект исключения распространяет стек вызовов.

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

подталкивания Exception также поддерживает N2179 стиль копирования исключений объектов, реализуемый ненавязчиво и автоматически с помощью функции подталкивания :: throw_exception.

+0

Я раньше не использовал импульс. Я посмотрю. Спасибо за сообщение. – Tom

+1

Pretty awesome библиотеки. Теперь мне жаль, что я не прочитал его раньше! –

17

мне было интересно, может быть, лучше было бы использовать множественное наследование, чтобы наследовать от каждого из производного станда :: классов исключений

Обратите внимание, что это проблема, из-за того, что исключения в стандартной библиотеке выводятся практически не из-за друг друга. Если вы вводите множественное наследование, вы получаете иерархию исключений из страшного алмаза без виртуального наследования и не сможете поймать производные исключения на std::exception&, так как ваш производный класс исключений несет два подобъекта std::exception, что делает std::exception «неоднозначным базовым классом».

Конкретный пример:

class my_exception : virtual public std::exception { 
    // ... 
}; 

class my_runtime_error : virtual public my_exception 
         , virtual public std::runtime_error { 
    // ... 
}; 

Теперь my_runtime_error производный (косвенно) от std::exception дважды, один раз через std::run_time_error и один раз через my_exception.Поскольку бывший не вытекает из std::exception практически, это

try { 
    throw my_runtime_error(/*...*/); 
} catch(const std::exception& x) { 
    // ... 
} 

не будет работать.

Edit:

Я думаю, что я видел первый пример иерархии исключений класса с участием MI в одной из книг Страуструп, поэтому я пришел к выводу, что, в общем, это хорошая идея. То, что исключения std lib не происходят практически друг от друга, я считаю неудачей.

Когда я последний раз проектировал иерархию исключений, я очень сильно использовал MI, но не получал из классов исключения std lib. В этой иерархии существовали абстрактные классы исключений, которые вы определили, чтобы ваши пользователи могли их поймать и соответствующие классы реализации, полученные из этих абстрактных классов и из базового класса реализации, которые вы на самом деле бросали. Чтобы сделать это проще, я определил некоторые шаблоны, которые будут делать всю тяжелую работу:

// something.h 
class some_class { 
private: 
    DEFINE_TAG(my_error1); // these basically define empty structs that are needed to 
    DEFINE_TAG(my_error2); // distinguish otherwise identical instances of the exception 
    DEFINE_TAG(my_error3); // templates from each other (see below) 
public: 
    typedef exc_interface<my_error1> exc_my_error1; 
    typedef exc_interface<my_error2> exc_my_error2; 
    typedef exc_interface<my_error3,my_error2> // derives from the latter 
            exc_my_error3; 

    some_class(int i); 
    // ... 
}; 

//something.cpp 
namespace { 
    typedef exc_impl<exc_my_error1> exc_impl_my_error1; 
    typedef exc_impl<exc_my_error2> exc_impl_my_error2; 
    typedef exc_impl<exc_my_error3> exc_impl_my_error3; 
    typedef exc_impl<exc_my_error1,exc_my_error2> // implements both 
            exc_impl_my_error12; 
} 
some_class::some_class(int i) 
{ 
    if(i < 0) 
    throw exc_impl_my_error3(EXC_INFO // passes '__FILE__', '__LINE__' etc. 
          , /* ... */ // more info on error 
          ); 
} 

Оглядываясь на это сейчас, я думаю, что я мог бы сделать что exc_impl шаблона класса проистекает из std::exception (или любого другого класса в std lib exception, передан как необязательный параметр шаблона), поскольку он никогда не происходит из любого другого экземпляра exc_impl. Но тогда это не было необходимо, поэтому мне это никогда не приходило в голову.

+0

+1: вполне объяснимо – fmuecke

+0

Спасибо за сообщение sbi. Будет ли создание классов исключений с множественным наследованием хорошей практикой? Или лучше просто наследовать из класса std :: exception? – Tom

+0

@Tom: Я добавил свои мысли в качестве редактирования. – sbi