Подтвердить что ты не робот

Конструкция класса исключений С++

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

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

Получение 1 и 4 для совместной работы - самая большая проблема, с которой я сталкиваюсь, поскольку любые методы форматирования и вывода файлов могут потенциально потерпеть неудачу.

EDIT: Поэтому, рассмотрев классы исключений в нескольких классах, а также в вопросе Нила, связанном с ним, кажется, что обычной практикой является просто игнорировать пункт 1 (и, следовательно, рекомендации по усилению), что представляется мне довольно плохой идеей.

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

class Exception : public std::exception
{
public:
    //enum for each exception type, which can also be used to determin
    //exception class, useful for logging or other localisation methods
    //for generating a message of some sort.
    enum ExceptionType
    {
        //shouldnt ever be thrown
        UNKNOWN_EXCEPTION = 0,
        //same as above but has a string that may provide some info
        UNKNOWN_EXCEPTION_STR,
        //eg file not found
        FILE_OPEN_ERROR,
        //lexical cast type error
        TYPE_PARSE_ERROR,
        //NOTE: in many cases functions only check and throw this in debug
        INVALID_ARG,
        //an error occured while trying to parse data from a file
        FILE_PARSE_ERROR,
    }
    virtual ExceptionType getExceptionType()const throw()
    {
        return UNKNOWN_EXCEPTION;
    }
    virtual const char* what()throw(){return "UNKNOWN_EXCEPTION";}
};
class FileOpenError : public Exception
{
public:
    enum Reason
    {
        FILE_NOT_FOUND,
        LOCKED,
        DOES_NOT_EXIST,
        ACCESS_DENIED
    };
    FileOpenError(Reason reason, const char *file, const char *dir)throw();
    Reason getReason()const throw();
    const char* getFile()const throw();
    const char* getDir ()const throw();
private:
    Reason reason;
    static const unsigned FILE_LEN = 256;
    static const unsigned DIR_LEN  = 256;
    char file[FILE_LEN], dir[DIR_LEN];
};

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

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

4b9b3361

Ответ 1

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

Выведите свои классы исключений из std:: exception (или одного из других стандартных исключений, таких как std:: runtime_error). Это позволяет использовать общие обработчики исключений на верхнем уровне для устранения каких-либо исключений. Например, может быть обработчик исключений, который регистрирует ошибки.

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

Я бы не делал слишком много классов исключений. Вы можете упаковать много подробностей об исключении в класс, поэтому вам необязательно создавать уникальный класс исключения для каждого типа ошибок. С другой стороны, вам нужны уникальные классы для ошибок, которые вы ожидаете обработать. Если вы создаете парсер, у вас может быть одно исключение синтаксиса_error с членами, которые описывают детали проблемы, а не кучу специальностей для разных типов синтаксических ошибок.

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

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

Исключения в С++ должны быть для "исключительных" условий. Таким образом, примеры синтаксического анализа могут быть не хорошими. Синтаксическая ошибка, возникающая при разборе файла, может быть не достаточно сложной, чтобы гарантировать обработку исключений. Я бы сказал, что что-то является исключительным, если программа, вероятно, не может продолжаться, если условие явно не обрабатывается. Таким образом, большинство сбоев в распределении памяти являются исключительными, но плохой ввод от пользователя, вероятно, не является.

Ответ 2

Использовать виртуальное наследование. Это понимание было вызвано Эндрю Кенигом. Использование виртуального наследования из вашего базового класса исключений предотвращает проблемы двусмысленности на сайте catch, если кто-то выбрал исключение, полученное из нескольких баз, которые имеют базовый класс.

Другие одинаково полезные советы на сайт повышения

Ответ 3


2: Нет, вы не должны смешивать пользовательский интерфейс (= локализованные сообщения) с логикой программы. Связь с пользователем должна выполняться на внешнем уровне, когда приложение понимает, что не может справиться с этой проблемой. Большая часть информации в Исключение составляет слишком много деталей реализации, чтобы показать пользователя в любом случае.
3: Используйте boost.exception для этого
5: Нет, не делайте этого. См. 2. Решение о регистрации всегда должно быть на сайте обработки ошибок.

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

Ответ 4

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

Это позволяет избежать проблем, связанных с управлением памятью заброшенного исключения (если вы указали указатели) и с возможностью среза объектов (если вы извлекаете исключения по значению).

Ответ 5

Хорошей конструкцией является не создание набора классов исключений - просто создайте один для каждой библиотеки на основе std:: exception.

Добавление информации довольно просто:

try {
  ...
}
catch( const MyEx & ex ) {
   throw MyEx( ex.what() + " more local info here" );
}

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

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

Это было задано раньше, я думаю, но я не могу найти его прямо сейчас.

Ответ 6

Поскольку std::nested_exception и std::throw_with_nested стали доступны с С++ 11, я хотел бы указать на ответы на StackOverflow здесь и здесь

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

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