2009-08-26 3 views
41

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

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

Получение 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, однако я думаю, что этот момент, скорее всего, ограничен в ограниченном использовании в реальном мире, и, скорее всего, его можно было бы устранить, если понадобится новое исключение.

ответ

2

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

Добавление информации довольно легко:

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

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

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

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

+1

Этот? http://stackoverflow.com/questions/1157591/c-exception-handling – GManNickG

+0

Это был тот, о котором я думал, хотя перечитывая его, кажется, отвечает на несколько разные вопросы. – 2009-08-26 15:40:39

+8

Что случилось с разными классами? Вы можете обращаться с ними по-другому или даже обрабатывать некоторых и оставлять других. –

7

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

Другие столь же полезные советы по boost site

+1

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

+0

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

+5

Кто-то, очевидно, пишет приложения, а не библиотеки. Структура является законной C++, и поэтому она будет использоваться независимо от того, что никто «не видит никакой причины использовать ее для классов исключений». Вы должны Jon up up, если не извинение - pgast 0 сек назад – pgast

7


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

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

23

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

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

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

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

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

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

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

+1

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

+5

Блок catch, который обрабатывает исключение, должен загружать строку пользовательского интерфейса из ресурсов. Что-то вроде: 'catch (const syntax_error & ex) {std :: cerr << ex.filename() <<" ("<< ex.line_number() <<"): "<< GetText (IDS_SYNTAXERROR) << std: : епсИ; } ', где' GetText() 'загружает строку из ресурсов вашей программы. –

+0

ли C++ 11 или C++ 17 что-нибудь изменит в вашем ответе? Меня больше интересует C++ 11, чем C++ 17. – Gizmo

4

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

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

0

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

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

Дизайн исключение там, на мой взгляд, также предлагает не создавать класс исключения иерархии, но создать только один класс исключений каждой библиотеки (как уже указывалось в an answer to this question).