Как разработать "типы" исключений в C++ [закрыто]

30
задан Peter Mortensen 1 October 2018 в 18:16
поделиться

2 ответа

Иерархия исключений в стандартной библиотеке C ++ IMHO довольно произвольна и бессмысленна. Например, это, вероятно, только создаст проблемы, если кто-то действительно начнет использовать, например, std::logic_error вместо завершения, когда становится ясно, что в программе имеется очень неприятная ошибка. Ибо, как гласит стандарт,

«Отличительной характеристикой логических ошибок является то, что они связаны с ошибками во внутренней логике программы».

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

Тем не менее, как и std::string, стандартная иерархия классов исключений имеет действительно очень важную и полезную функцию, а именно то, что она формально стандартна .

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

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

& ldquo; ошибки времени выполнения происходят из-за событий, выходящих за рамки программы. Они не могут быть легко предсказаны заранее.

Иногда механизм исключений C ++ используется для других вещей, рассматриваемых как просто низкоуровневый механизм динамического перехода назначения. Например, умный код может использовать исключения для распространения успешного результата из цепочки рекурсивных вызовов. Но исключение-как-сбой является наиболее распространенным использованием, и именно для этого обычно оптимизируются исключения C ++, поэтому в большинстве случаев имеет смысл использовать std::runtime_error в качестве root для любой пользовательской иерархии классов исключений & ndash; даже если это вынуждает того, кто хочет быть умным, генерировать исключение, указывающее на неудачу, чтобы указать на успех

Стоит отметить: есть три стандартных подкласса std::runtime_error, а именно std::range_error, std::overflow_error и std::underflow_error, и что в отличие от того, что их имена указывают, последние два не обязательно должны генерироваться операциями с плавающей запятой и на практике не генерируются операциями с плавающей запятой, а AFAIK генерируются только некоторыми & ndash; сюрприз! & Ndash; std::bitset операции. Проще говоря, стандартная иерархия классов исключений библиотеки, как мне кажется, была добавлена ​​туда просто ради внешнего вида, без каких-либо реальных уважительных причин или существующей практики, и даже без проверки «сделай сам». Но, возможно, я упустил это, и если так, то у меня еще есть что-то новое, чтобы узнать об этом. : -)

Итак, std::runtime_error так и есть.

На вершине иерархии пользовательских классов исключений, в C ++ 03 было полезно добавить важные вещи, отсутствующие в стандартных исключениях C ++ 03:

  • Virtual clone метод (особенно важный для передачи исключений через код C).

  • Виртуальный метод throwSelf (та же основная причина, что и для клонирования).

  • Поддержка связанных сообщений об исключениях (стандартизация формата).

  • Поддержка переноса кода причины сбоя (например, кода ошибки Windows или Posix).

  • Поддержка получения стандартного сообщения из перенесенного кода причины сбоя.

В C ++ 11 добавлена ​​поддержка большей части этого, но за исключением опробования новой поддержки кодов и сообщений о причинах сбоев, и отмечая, что, к сожалению, она довольно специфична для Unix и не очень подходит для Windows, Я еще не использовал его. В любом случае, для полноты: вместо добавления клонирования и виртуального повторного броска (что является лучшим, что обычный программист приложения может сделать в пользовательской иерархии классов исключений, потому что как программист приложения вы не можете вытащить текущий объект исключения из хранилища, в котором реализация & rsquo ; s использует распространение исключений), стандарт C ++ 11 добавляет свободные функции std::current_exception() и std::rethrow_exception(), и вместо поддержки связанных сообщений об исключениях он добавляет класс mixin std::nested_exception и свободные функции std::rethrow_nested и std::rethrow_if_nested .

Учитывая частичную поддержку C ++ 11 для вышеупомянутых пунктов, новая и современная иерархия пользовательских классов исключений должна лучше интегрироваться с поддержкой C ++ 11 вместо устранения недостатков C ++ 03. Ну, за исключением кода ошибки C ++ 11, который кажется очень неподходящим для программирования Windows. Таким образом, в верхней части пользовательской иерархии, прямо под std::runtime_error, в идеале должен быть хотя бы один общий класс исключений и, как следствие этого, один класс исключений, который поддерживает распространение кодов сбоев.

Теперь, наконец, суть вопроса: лучше ли теперь получить уникальный класс исключений для каждой возможной причины отказа или, по крайней мере, для основных причин отказа?

Я говорю нет: НЕ ДОБАВЛЯЙТЕ НИКАКОЙ СЛОЖНОСТИ .

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

Но как насчет кодов причин сбоя?

Ну, когда это то, что дает вам базовый API, это просто добавленная работа по созданию соответствующих классов исключений. Но с другой стороны, когда вы сообщаете о сбое в цепочке вызовов, и вызывающему абоненту может потребоваться узнать точную причину, тогда использование кода для этого означает, что вызывающему абоненту придется использовать некоторые вложенные проверки и диспетчеризацию внутри catch. Таким образом, это разные ситуации: (A) ваш код является исходным источником индикации сбоя, по сравнению с (B) вашим кодом, например. неработающая функция Windows или Posix API, указывающая причину сбоя с помощью кода причины сбоя.

30
ответ дан 28 November 2019 в 00:15
поделиться

Если условие ошибки - это то, что вызывающая сторона вашей библиотеки могла бы предотвратить, изменив логику своего кода, то выведите свое исключение из logic_error. Обычно вызывающая сторона не сможет выполнить простую повторную попытку, если выбрано logic_error. Например, кто-то вызывает ваш код так, что он вызовет деление на 0, тогда вы можете создать пользовательское исключение,

class divide_by_zero : public logic_error {
public:
    divide_by_zero(const string& message)
        : logic_error(message) { 
    }
}

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

class network_down : public runtime_error {
public:
    network_down(const string& message)
        : runtime_error(message) { 
    }
}

Это также общая философия для разработки исключений в стандартной библиотеке. Вы можете просмотреть код исключения для GCC здесь .

3
ответ дан 28 November 2019 в 00:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: