Когда вы должны создать свой собственный тип исключения?

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

Мы создаем разные типы исключений для разных модулей.

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

Подумав об этом, я наткнулся на этот вопрос:

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

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

Ответ 1

Создайте свои собственные типы исключений, когда:

  1. вы можете различать их при обращении. Если они разные, у вас есть возможность написать разные предложения catch. У них должна быть общая база, поэтому вы можете иметь общую обработку, когда это уместно
  2. вы хотите добавить некоторые конкретные структурированные данные, которые вы действительно можете использовать в обработчике

Наличие отдельных list и vector исключений не представляется целесообразным, если только в них нет отчетливо-подобных или векторных. Вы действительно будете иметь различную обработку перехвата в зависимости от того, какой тип контейнера имел ошибку?

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

Ответ 2

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

Использование выделенного исключения для всех различных модулей добавляет несколько преимуществ:

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

Ответ 3

Я думаю, что в дополнение к причинам в других ответах может быть читаемость кода, потому что много времени программисты проводят ее поддержку. Рассмотрим два фрагмента кода (при условии, что они выбрасывают ошибку "пустой кадр"):

void MyClass::function() noexcept(false) {

    // ...
    if (errorCondition) {
        throw std::exception("Error: empty frame");
    }
}
void MyClass::function() noexcept(false) {

    // ...
    if (errorCondition) {
        throw EmptyFrame();
    }
}

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

Ответ 4

Я вижу две возможные причины.

1) Если вы хотите сохранить в классе исключения некоторую настраиваемую информацию об исключении, вам нужно исключение из дополнительных членов данных. Часто вы наследуете один из классов исключений std.

2) Если вы хотите различать исключения. Например, предположим, что у вас есть две отличные invalid argument ситуации с invalid argument и в вашем блоке catch вы хотите разграничить их. Вы можете создать два дочерних класса std::invalid_argument

Ответ 5

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

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

Нечестивым альянсом будет построение иерархии исключений:

std::exception
  EXProjectBase
    EXModuleBase
      EXModule1
      EXModule2
    EXClassBase
      EXClassMemory
      EXClassBadArg
      EXClassDisconnect
      EXClassTooSoon
      EXClassTooLate

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

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

Ответ 6

Я бы спросил, из раздела try...catch, "знает ли этот код, как восстановить ошибку, или нет?

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

  1. Добавьте код, чтобы исключить функцию родительской функции. Многое должно быть сделано тихо с RAII в деструкторах, но иногда (например, операция перемещения) требуется больше работы для отката частично завершенной работы, это зависит от уровня безопасности исключений, который вы хотите.
  2. Извлеките или добавьте некоторую отладочную информацию и восстановите исключение.
  3. Попытайтесь восстановиться после ошибки.

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

try {
    ....
} catch (const std::exception & e) {
    // print or tidy up or whatever
    throw;
} catch (...) {
    // print or tidy up or whatever
    // also complain that std::exception should have been thrown
    throw;
}

По моему опыту, это, вероятно, будет на 99% случаев реального мира.

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

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

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