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

98

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

В C # причины наследования от ApplicationExceptionочевидны: вы получаете несколько полезных методов, свойств и конструкторов, и вам просто нужно добавить или переопределить то, что вам нужно. С std::exceptionэтим , кажется , что все , что вы получите это what()метод , чтобы переопределить, который вы могли бы точно так же создавать самостоятельно.

Итак, каковы преимущества, если таковые имеются, от использования std::exceptionв качестве базового класса для моего класса исключений для конкретного приложения? Есть ли веские причины не наследовать std::exception?

Джон М. Гант
источник
Возможно, вы захотите взглянуть на это: stackoverflow.com/questions/1605778/1605852#1605852
sbi
2
Хотя в качестве побочного примечания, не связанного с конкретным вопросом, классы C ++, которые вы изучаете, не обязательно должны быть надежными источниками хороших практик только сами по себе.
Christian Rau

Ответы:

69

Основное преимущество состоит в том , что код , использующий классы не должны знать точный тип того , что вы throwна него, а может просто . Изменить: как отметили Мартин и другие, вы действительно хотите унаследовать от одного из подклассов, объявленных в заголовке.catchstd::exception

std::exception<stdexcept>

Николай Фетисов
источник
21
Невозможно передать сообщение в std :: exception. std :: runtime_error принимает строку и является производным от std :: exception.
Мартин Йорк
14
Вы не должны передавать сообщение конструктору типа исключения (учтите, что сообщения должны быть локализованы). Вместо этого определите тип исключения, который семантически классифицирует ошибку, сохраните в объекте исключения то, что вам нужно, чтобы отформатировать удобное для пользователя сообщение. , затем сделайте это на месте улова.
Эмиль
std::exceptionкак определено в <exception>заголовке ( доказательство ). -1
Александр Шукаев
5
Так в чем твоя точка зрения? Определение подразумевает декларацию, что, по вашему мнению, здесь не так?
Николай Фетисов
40

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

В результате я предпочитаю выводить из std::runtime_error. Это является производным от, std::exceptionно его конструкторы позволяют передавать C-String или a std::stringконструктору, который будет возвращен (как a char const*) при what()вызове.

Мартин Йорк
источник
11
Не рекомендуется форматировать удобное для пользователя сообщение в момент выброса, потому что это приведет к объединению кода нижнего уровня с функциями локализации и прочим. Вместо этого сохраните в объекте исключения всю необходимую информацию и позвольте сайту перехвата форматировать удобное для пользователя сообщение на основе типа исключения и данных, которые он передает.
Эмиль
16
@ Эмиль: Конечно, если вы являетесь исключением, вы несете сообщения, отображаемые пользователем, хотя обычно они предназначены только для ведения журнала. На сайте throw у вас нет контекста для создания сообщения пользователя (так как это может быть библиотека). Сайт перехвата будет иметь больше контекста и может генерировать соответствующее сообщение msg.
Мартин Йорк
3
Не знаю, откуда вы взяли, что исключения предназначены только для ведения журнала. :)
Эмиль
1
@Emil: Мы уже обсуждали этот аргумент. Где я указал, что это не то, что вы добавляете в исключение. Вы понимаете предмет, кажется, хорошо, но ваши аргументы ошибочны. Сообщения об ошибках, создаваемые для пользователя, полностью отличаются от сообщений журнала для разработчика.
Мартин Йорк
1
@ Эмиль. Этому обсуждению год. На этом этапе создайте свой вопрос. Ваши аргументы, насколько я понимаю, просто ошибочны (и, судя по голосам, люди склонны со мной соглашаться). См. Какое количество исключений следует реализовать для моей библиотеки? и действительно ли перехват общих исключений - это плохо? Вы не предоставили никакой новой информации с момента вашей предыдущей обличительной речи.
Мартин Йорк
17

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

Если вам нужно удобство, вы можете унаследовать от std::runtime_errorэтого std::stringконструктора.

Алексей Потов
источник
2
Наследование из любого другого стандартного типа исключения, кроме std :: exception, вероятно, не является хорошей идеей. Проблема в том, что они происходят от std :: exception не виртуально, что может привести к тонким ошибкам, связанным с множественным наследованием, когда catch (std :: exception &) может молча не перехватить исключение из-за того, что преобразование в std :: exception неоднозначный.
Эмиль
2
Прочитал по этой теме статью про буст. Это кажется разумным в чисто логическом смысле. Но я никогда не видел MH в исключительных случаях в дикой природе. Я также не стал бы рассматривать использование MH в исключениях, поэтому это похоже на кувалду для решения несуществующей проблемы. Если бы это была реальная проблема, я уверен, что мы бы увидели меры по ней со стороны комитета по стандартам, чтобы исправить такую ​​очевидную ошибку. Итак, я считаю, что std :: runtime_error (и семейство) по-прежнему являются вполне приемлемым исключением для создания или наследования.
Мартин Йорк
5
@Emil: Использование других стандартных исключений std::exception- отличная идея. Чего вам не следует делать, так это наследовать более чем от одного.
Mooing Duck
@MooingDuck: если вы производите более одного стандартного типа исключения, вы получите std :: exception, полученный (не виртуально) несколько раз. См. Boost.org/doc/libs/release/libs/exception/doc/… .
Эмиль
1
@Emil: Это следует из того, что я сказал.
Mooing Duck
13

Однажды я участвовал в очистке большой базы кода, куда предыдущие авторы добавляли целые числа, HRESULTS, std :: string, char *, случайные классы ... везде разные вещи; просто назовите тип, и он, вероятно, был куда-то брошен. И вообще никакого общего базового класса. Поверьте, все стало намного аккуратнее, когда мы дошли до того, что все брошенные типы имели общую базу, которую мы могли поймать и знать, что ничего не пройдет. Поэтому, пожалуйста, сделайте себе (и тем, кому придется поддерживать ваш код в будущем) одолжение и сделайте это с самого начала.

Timday
источник
12

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

Джеймс Шек
источник
5
Однако обратите внимание, что само boost :: exception не является производным от std :: exception. Даже при наследовании от boost :: exception вы также должны быть производным от std :: exception (в качестве отдельного примечания, всякий раз, когда происходит наследование от типов исключений, вы должны использовать виртуальное наследование.)
Эмиль
10

Да, вы должны исходить из std::exception .

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

Эмиль
источник
6
+1, хорошее замечание. Как с точки зрения разделения проблем, так и с точки зрения i18n, определенно лучше позволить уровню представления создавать пользовательское сообщение.
Джон М. Гант,
@JohnMGant и Эмиль: Интересно, не могли бы вы указать на конкретный пример того, как это можно сделать. Я понимаю, что можно извлечь std::exceptionи передать информацию об исключении. Но кто будет отвечать за создание / форматирование сообщения об ошибке? ::what()Функция или что - то еще?
alfC
1
Вы бы отформатировали сообщение на сайте перехвата, скорее всего, на уровне приложения (а не библиотеки). Вы ловите его по типу, затем исследуете его для получения информации, а затем локализуете / форматируете соответствующее сообщение пользователя.
Эмиль
9

Причина, по которой вы можете захотеть наследовать, std::exceptionзаключается в том, что это позволяет вам генерировать исключение, которое перехватывается в соответствии с этим классом, то есть:

class myException : public std::exception { ... };
try {
    ...
    throw myException();
}
catch (std::exception &theException) {
    ...
}
SingleNegationElimination
источник
6

Есть одна проблема с наследованием, о которой вы должны знать, это нарезка объектов. Когда вы пишете throw e;throw-выражение, инициализирует временный объект, называемый объектом исключения, тип которого определяется удалением любых cv-квалификаторов верхнего уровня из статического типа операнда throw. Это может быть не то, что вы ожидаете. Пример проблемы вы можете найти здесь .

Это не аргумент против наследования, это просто информация, которую нужно знать.

Кирилл Васильевич Лядвинский
источник
3
Я думаю, что главное - бросить е; зло, и "бросить"; в порядке.
Джеймс Шек
2
Да, throw;это нормально, но не очевидно, что вы должны написать что-то подобное.
Кирилл В. Лядвинский 03
1
Это особенно болезненно для разработчиков Java, когда повторное скачивание выполняется с помощью throw e;
Джеймс Шек
Рассматривайте только наследование абстрактных базовых типов. Поймание абстрактного базового типа по значению даст вам ошибку (избегая нарезки); перехват абстрактного базового типа по ссылке с последующей попыткой выбросить его копию приведет к ошибке (опять же без нарезки)
Эмиль
Вы также можете решить эту проблему путем добавления функции - члена, raise()как таковая: virtual void raise() { throw *this; }. Просто не забудьте переопределить его в каждом производном исключении.
Джастин Тайм - Восстановить Монику
5

Разница: std :: runtime_error vs std :: exception ()

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

Муравей
источник
3

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

Другими словами, это либо

catch (...) {cout << "Unknown exception"; }

или

catch (const std::exception &e) { cout << "unexpected exception " << e.what();}

И второй вариант однозначно лучше.


источник
3

Если все ваши возможные исключения являются производными std::exception, ваш блок catch может catch(std::exception & e)быть просто уверен в том, что захватит все.

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

Марк Рэнсом
источник
7
не создавайте подклассы std :: exception, а затем перехватывайте (std :: exception e). Вам нужно поймать ссылку на std :: exception & (или указатель), иначе вы отрезаете данные подкласса.
jmucchiello
1
Это было глупой ошибкой - конечно, я знаю лучше. stackoverflow.com/questions/1095225/…
Марк Рэнсом
3

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

После того, как вы решили, производиться от стандартного типа исключения или нет, возникает вопрос, что должно быть базовым. Если вашему приложению не нужен i18n, вы можете подумать, что форматирование сообщения на сайте вызова ничуть не хуже сохранения информации и генерации сообщения на сайте вызова. Проблема в том, что форматированное сообщение может не понадобиться. Лучше использовать ленивую схему генерации сообщений - возможно, с предварительно выделенной памятью. Затем, если сообщение понадобится, оно будет сгенерировано при доступе (и, возможно, кэшировано в объекте исключения). Таким образом, если сообщение создается при вызове, тогда в качестве базового класса требуется производное std :: exception, например std :: runtime_error. Если сообщение создается лениво, подходящей базой является std :: exception.

Роб
источник
0

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

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

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

Джеймс Бёрнби
источник
0

Хотя этот вопрос довольно старый и уже получил много ответов, я просто хочу добавить примечание о том, как правильно обрабатывать исключения в C ++ 11, поскольку я постоянно упускаю его в обсуждениях исключений:

Использование std::nested_exceptionиstd::throw_with_nested

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

Так как вы можете сделать это с любым производным классом исключений, вы можете добавить много информации в такую ​​трассировку! Вы также можете взглянуть на мою MWE на GitHub , где трассировка будет выглядеть примерно так:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

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

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

try
{
  // something that may throw
}
catch( const MyException & ex )
{
  // do something specialized with the
  // additional info inside MyException
}
catch( const std::exception & ex )
{
  std::cerr << ex.what() << std::endl;
}
catch( ... )
{
  std::cerr << "unknown exception!" << std::endl;
}
Г.П.Мюллер
источник