Нужно ли наследовать от std :: exception?

15

При разработке моей первой «серьезной» библиотеки C ++ я спрашиваю себя:

Это хороший стиль, чтобы извлечь свои исключения, std::exceptionи это потомки ?!

Даже после прочтения

Я все еще не уверен. Потому что, помимо обычной (но, возможно, не очень) практики, я бы предположил, как пользователь библиотеки, что библиотечная функция будет генерировать std::exceptions только в случае сбоя стандартных библиотечных функций в реализации библиотеки, и она ничего не может с этим поделать. Но тем не менее, при написании кода приложения, для меня это очень удобно, а также ИМХО хорошо выглядеть просто бросить std::runtime_error. Также мои пользователи также могут рассчитывать на определенный минимальный интерфейс, например what()или коды.

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

Мысли?

Superlokkus
источник
Я не уверен, что следую. Просто потому , что вы унаследовали от std::exceptionне означает , что вы броситьstd::exception . Кроме того, std::runtime_errorнаследуется ли std::exceptionв первую очередь от what()метода std::exception, а не от std::runtime_error. И вам определенно следует создавать свои собственные классы исключений, а не генерировать общие исключения, такие как std::runtime_error.
Винсент Савард
3
Разница в том, что когда мой lib_foo_exceptionкласс наследует от std::exceptionпользователя, пользователь библиотеки будет ловить lib_foo_exception, просто ловя std::exception, в дополнение к тому, когда он ловит только библиотечный. Поэтому я мог бы также спросить, должен ли мой корневой класс исключения библиотеки наследоваться от std :: exception .
Суперлоккус
3
@LightnessRacesinOrbit Я имею в виду «... в дополнение к», например, «Сколько способов поймать lib_foo_exception?» С наследованием от std::exceptionвас это можно сделать catch(std::exception)ИЛИ путем catch(lib_foo_exception). Не получая от этого std::exception, вы бы поймали его тогда и только тогда , когда catch(lib_foo_exception).
Суперлоккус
2
@Superlokkus: Мы вроде игнорируем catch(...). Это происходит потому, что язык учитывает тот случай, который вы рассматриваете (и «недостойное поведение» библиотек), но это не лучшая современная практика.
Легкость гонок с Моникой
1
Большая часть дизайна обработки исключений в C ++ имеет тенденцию поощрять более грубые, более общие catchсайты, а также более грубые транзакции, которые моделируют пользовательскую операцию. Если вы сравните его с языками, которые не продвигают идею обобщенного перехвата std::exception&, например, они часто имеют гораздо больше кода с промежуточными try/catchблоками, связанными с очень специфическими ошибками, что несколько уменьшает общность обработки исключений, поскольку она начинает появляться гораздо больший упор на ручную обработку ошибок, а также на все несопоставимые ошибки, которые могут произойти.

Ответы:

29

Все исключения должны наследоваться от std::exception.

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

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
}

Если исключения НЕ наследуются std::exception, это становится намного хуже:

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
} catch (Exception& e) {
    cerr << e.Message << endl;
} catch (framework_exception& e) {
    cerr << e.Details() << endl;
}

Относительно того, выбрасывать runtime_errorили invalid_argumentсоздавать собственные std::exceptionподклассы, чтобы выбрасывать: мое эмпирическое правило - вводить новый подкласс всякий раз, когда мне нужно обрабатывать определенный тип ошибок иначе, чем другие ошибки (то есть всякий раз, когда мне нужен отдельный catchблок).

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

В Основных принципах C ++ продолжить обсуждение и примеры.

Джош Келли
источник
Все эти амперсанды! C ++ странный.
SuperJedi224
2
@ SuperJedi224 и все эти разные буквы! Английский странный
Йоханнес
Это обоснование не имеет смысла для меня. Разве не для этого catch (...)(с буквальным многоточием)?
Maxpm
1
@Maxpm catch (...)полезен, только если вам не нужно ничего делать с тем, что выкинули. Если вы хотите что-то сделать - например, показать или записать конкретное сообщение об ошибке, как в моем примере - тогда вам нужно знать, что это такое.
Джош Келли
9

Я бы предположил, как пользователь библиотеки, что библиотечная функция будет генерировать std :: excptions только тогда, когда стандартные библиотечные функции потерпели неудачу в реализации библиотеки, и она ничего не может с этим поделать

Это неверное предположение.

Стандартные типы исключений предоставляются для «обычного» использования. Они не предназначены для только использования стандартной библиотеки.

Да, сделать все в конечном итоге наследовать std::exception. Часто это включает в себя наследование от std::runtime_errorили std::logic_error. Все, что подходит для класса исключений, который вы реализуете.

Это, конечно, субъективно - некоторые популярные библиотеки полностью игнорируют стандартные типы исключений, предположительно для отделения библиотек от стандартной библиотеки. Лично я считаю, что это очень эгоистично! Это делает отлов исключений, которые намного сложнее понять.

Говоря лично, я часто просто бросаю std::runtime_errorи покончу с этим. Но это вступает в дискуссию о том, как гранулярно создавать классы исключений, а это не то, о чем вы просите.

Гонки легкости с Моникой
источник