Насколько худшими мои классы С++-классов действительно нужны?

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

Например, Boost люди рекомендуют, чтобы класс не содержал элементов std::string, потому что их конструктор мог бы выбросить, что вызовет запуск -time, чтобы немедленно завершить программу.

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

С учетом этого, почему я не должен вставлять объекты std::string в мои классы исключений? Фактически, почему мои классы исключений не могут быть полнофункциональными, а также заботиться о регистрации, трассировке стека и т.д. Я знаю принцип единой ответственности, и мне кажется, что это справедливый компромисс чтобы класс исключения выполнял все это. Конечно, если мой парсер должен сообщить о синтаксической ошибке, полнофункциональное исключение было бы более полезным, чем исключение, построенное вокруг статически выделенного массива символов.

Итак: худые классы исключений С++ - насколько велика сделка в реальном мире? Каковы компромиссы? Есть ли хорошие дискуссии по этой теме?

Ответ 1

Вы можете использовать библиотеку Boost.Exception, чтобы определить иерархию исключений. Библиотека Boost.Exception поддерживает:

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

Ограничения структуры предоставят вам разумно определенные параметры дизайна.

Boost.Exception
См. Также: Boost.System

Ответ 2

В общем случае классы исключений должны быть простыми, самодостаточными структурами и никогда не выделять память (например, std::string). Первая причина заключается в том, что распределения или другие сложные операции могут быть неудачными или иметь побочные эффекты. Другая причина заключается в том, что объекты исключений передаются по значению и, следовательно, распределяются по стеклу, поэтому они должны быть как можно более легкими. Функции более высокого уровня должны обрабатываться кодом клиента, а не самим классом исключений (если только для цели отладки).

Ответ 3

Задача номер один для исключения, задолго до того, как кто-либо подумает о том, чтобы разрешить коду обрабатывать исключение, заключается в том, чтобы иметь возможность сообщать пользователю и/или разработчику о том, что пошло не так. Класс исключений, который не может сообщить о OOM, но просто сбой программы, не указав, почему он разбился, не стоит того. OOM становится довольно распространенным в наши дни, у 32-разрядной виртуальной памяти заканчивается газ.

Проблема с добавлением большого количества вспомогательных методов в класс исключений заключается в том, что он заставит вас в иерархию классов, которые вам необязательно нужны или нужны. Теперь требуется получение из std:: exception, поэтому вы можете что-то сделать с помощью std:: bad_alloc. У вас возникнут проблемы при использовании библиотеки, в которой есть классы исключений, которые не происходят из std:: exception.

Ответ 4

Посмотрите на исключения std, которые все они используют std::string внутри.
(Или я должен сказать, что моя реализация g++ делает, я уверен, что в стандарте не говорится об этой проблеме)

/** Runtime errors represent problems outside the scope of a program;
  *  they cannot be easily predicted and can generally only be caught as
  *  the program executes.
  *  @brief One of two subclasses of exception.
 */
class runtime_error : public exception
{
    string _M_msg;
  public:
    /** Takes a character string describing the error.  */
    explicit runtime_error(const string&  __arg);

    virtual ~runtime_error() throw();

    /** Returns a C-style character string describing the general cause of
     *  the current error (the same string passed to the ctor).  */
    virtual const char* what() const throw();
};

Я обычно получаю свои исключения из runtime_error (или одного из других стандартных исключений).

Ответ 5

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

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

С учетом этого, почему я не должен вставлять объекты std::string в мои классы исключений? Фактически, почему мои классы исключений не могут быть полнофункциональными, а также заботиться о регистрации, трассировке стека и т.д. Я знаю принцип единой ответственности, и мне кажется, что это справедливый компромисс чтобы класс исключения выполнял все это.

Почему это справедливый компромисс? Почему это компромисс? Компромисс подразумевает, что вы делаете некоторые уступки принципу единой ответственности, но, насколько я вижу, вы этого не делаете. Вы просто говорите: "мое исключение должно делать все". Это вряд ли компромисс.

Как всегда с SRP, ответ должен быть очевиден: что вы получаете, создавая класс исключений, чтобы сделать все? Почему регистратор не может быть отдельным классом? Почему это должно быть выполнено за исключением? Не следует ли обрабатывать обработчик исключений? Вы также можете захотеть локализовать и предоставить сообщения об ошибках синтаксиса на разных языках. Таким образом, ваш класс исключений должен при построении выходить и читать внешние файлы ресурсов, ища правильные локализованные строки? Это, конечно, означает еще один потенциальный источник ошибок (если строка не может быть найдена), добавляет больше сложности для исключения и требует, чтобы исключение знало иначе несущественную информацию (какой язык и настройки локали пользователь использует). И отформатированное сообщение об ошибке может зависеть от того, как оно отображается. Возможно, он должен быть отформатирован по-разному при регистрации, когда он отображается в окне сообщения или при печати на стандартный вывод. Дополнительные проблемы для класса исключений. И больше вещей, которые могут пойти не так, больше кода, где могут возникнуть ошибки.

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

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

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

Однако правило, что "класс исключения не должен содержать std::string, не совсем то же самое, что" классу исключений не разрешено выделять память ". std::exception делает последнее. Он хранит C- string. В конце концов, Boost просто не запоминает объекты, которые могут генерировать исключения. Поэтому, если вы выделяете память, вам просто нужно иметь дело с случаем, при котором происходит сбой.

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

Говорит тот, кто только что сказал, что он не против приложения, которое просто заканчивается без обратной связи с пользователем, если произошла ошибка.;)

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

  • Имя входного файла (или дескриптор или указатель, который позволяет нам при необходимости получать имя файла)
  • Номер строки, в которой произошла ошибка.
  • Возможно, позиция на линии
  • Тип произошедшей синтаксической ошибки.

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

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

Ответ 6

Стандарт С++ требует, чтобы в исключениях не было метаданных с копированием. Если у вас есть член std::string, у вас нет конструктора копии без броска. Если система не может скопировать ваше исключение, она завершит вашу программу.

Также полезно использовать виртуальное наследование при проектировании вашей иерархии типов исключений, как описано в http://www.boost.org/doc/libs/release/libs/exception/doc/using_virtual_inheritance_in_exception_types.html.

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

Ответ 7

Я думаю, что отказ от использования std::string в классах исключений - ненужный пуризм. Да, это может бросить. И что? Если ваша реализация std::string выдает по причинам, отличным от нехватки памяти, только потому, что вы создаете сообщение "Невозможно разобрать файл Foo", то что-то не так с реализацией, а не с вашим кодом.

Что касается исчерпания памяти, эта проблема возникает даже при создании исключения, которое не принимает строковых аргументов. Добавление 20 байтов полезного сообщения об ошибке вряд ли вызовет или нарушит все. В настольном приложении большинство ошибок OOM происходят, когда вы пытаетесь выделить 20 ГБ памяти по ошибке, а не потому, что вы счастливо работали с мощностью 99,9999%, и что-то подсказывало вам сверху.

Ответ 8

... исчерпание памяти является фатальным для моего приложения независимо от того, что.

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

Оставив в стороне марсоходов, подумайте о таком простом случае, как писать текстовый процессор. Вы хотите реализовать функцию копирования/вставки. Пользователь выбирает текст и нажимает Ctrl + C. Какой результат вы предпочитаете: авария или сообщение "Недостаточно памяти"? (Если не было достаточного количества памяти для отображения окна сообщения, ничего не происходит.) Второй сценарий, без сомнения, более удобен для пользователя, она может просто закрыть другое приложение и продолжить работу над ее документом.

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

class my_exception : virtual public std::exception {
public:
    // public interface...
private:
    shared_ptr<internal> representation;
};

Предполагая, что исключения относятся к исключительным ситуациям, накладные расходы атома незначительны. На самом деле это то, что делает Boost.Exception.

С учетом сказанного я также рекомендую ответить на jalf.