Чи слід успадковувати від std :: виключення?


98

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

У C # причини успадкування з ApplicationExceptionних ясні: ви отримуєте кілька корисних методів, властивостей та конструкторів і просто повинні додати або переотримати те, що вам потрібно. З std::exceptionцим , здається , що все , що ви отримаєте це what()метод , щоб перевизначити, який ви могли б точно так же створювати самостійно.

Тож які переваги можна використовувати std::exceptionв якості базового класу для мого додаткового класу виключень? Чи є вагомі причини, щоб не успадкувати std::exception?


Ви можете подивитися на це: stackoverflow.com/questions/1605778/1605852#1605852
sbi

2
Хоча як побічна примітка, не пов'язана з конкретним питанням, заняття C ++, які ви берете, не обов'язково повинні бути надійними джерелами про добру практику лише поза власним правом.
Крістіан Рау

Відповіді:


69

Основна перевага полягає в тому , що код , який використовує класи не повинні знати точний тип того , що ви throwна нього, а може просто . Редагувати: як зазначали Мартін та інші, ви насправді хочете походити з одного з підкласів, оголошених у заголовку.catchstd::exception

std::exception<stdexcept>


21
Немає способу передавати повідомлення до std :: виключення. std :: runtime_error приймає рядок і походить від std :: виключення.
Мартін Йорк

14
Ви не повинні передавати повідомлення конструктору типів винятків (врахуйте, що повідомлення потрібно локалізувати.) Натомість визначте тип винятку, який класифікує помилку семантично, зберігайте в об’єкті винятку те, що вам потрібно для форматування зручного повідомлення , зробіть це на місці вилову.
Еміль

std::exceptionяк визначено в <exception>заголовку ( доказ ). -1
Олександр Шукаєв

5
Тож у чому тобі справа? Визначення означає декларацію, що ви бачите тут не так?
Микола Фетисов

40

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

У результаті я вважаю за краще походити std::runtime_error. Це походить від, std::exceptionале його конструктори дозволяють передати C-String або a std::stringдо конструктора, який буде повернутий (як char const*) при what()виклику.


11
Це не дуже гарна ідея відформатувати дружнє для користувача повідомлення в точці кидка, оскільки це поєднає код нижчого рівня з функціоналом локалізації та ще. Натомість, зберігайте в об’єкті винятку всю релевантну інформацію та нехай сайт лову відтворює зручне повідомлення на основі типу винятку та даних, які він несе.
Еміль

16
@ Emil: Безперечно, якщо ви є виключенням, переносите повідомлення, які відображаються користувачем, хоча вони, як правило, призначені лише для реєстрації. На сайті кидка у вас немає контексту для створення повідомлення користувача (оскільки це все одно може бути бібліотека). Сайт улову матиме більше контексту і може генерувати відповідну msg.
Мартін Йорк

3
Я не знаю, звідки у вас ідея, що винятки призначені лише для ведення журналів. :)
Еміль

1
@Emil: Ми вже пройшли цей аргумент. Де я зазначив, що це не те, що ви ставите винятком. Ви розумієте тему добре, але ваш аргумент хибний. Повідомлення про помилки, створені для користувача, абсолютно не відрізняються від повідомлень журналу для розробника.
Мартін Йорк

1
@Emil. Це обговорення рік. Створіть власне запитання в цей момент. Що стосується мене, то ваші аргументи просто помилкові (і на основі голосів люди, як правило, погоджуються зі мною). Див. Що таке "велика кількість" винятків для моєї бібліотеки? і чи справді ловити загальні винятки - це погано? Ви не надали жодної нової інформації з моменту попереднього діатрибу.
Мартін Йорк

17

Причина успадкування std::exception- це "стандартний" базовий клас для винятків, тому, звичайно, інші люди в команді, наприклад, сподіватися на це і вловлювати базу std::exception.

Якщо ви шукаєте зручності, ви можете успадкувати std::runtime_errorте, що надає std::stringконструктор.


2
Виведення з будь-якого іншого типового винятку, за винятком std :: виключення, мабуть, не є хорошою ідеєю. Проблема полягає в тому, що вони походять від std :: виключення невіртуально, що може призвести до тонких помилок, що включають багатократне успадковування, коли улов (std :: виключення &) може мовчки не вдатися до вилучення виключення через перетворення в std :: неоднозначне.
Еміль

2
Я прочитав статтю про підвищення по цій темі. Здається розумним у чистому логічному сенсі. Але я ніколи не бачив МЗ за винятком у дикій природі. Я також не розглядаю можливість використання MH за винятками, тому здається, що кувалда виправляє неіснуючу проблему. Якби це була справжня проблема, я впевнений, що ми побачили б дії комітету зі стандартів, щоб виправити такий очевидний недолік. Тож моя думка полягає в тому, що std :: runtime_error (і сімейні) все ще є цілком прийнятним винятком для кидання або отримання з нього.
Мартін Йорк

5
@Emil: Окрім інших стандартних винятків std::exception, відмінна ідея. Те, що ви не повинні робити, успадковується від більш ніж одного.
Mooing Duck

@MooingDuck: Якщо ви походите з декількох стандартних типів винятків, у кінцевому підсумку виводиться (не-практично) std :: виключення кілька разів. Див boost.org/doc/libs/release/libs/exception/doc / ... .
Еміль

1
@Emil: Це випливає з того, що я сказав.
Mooing Duck

13

Я колись брав участь у очищенні великої бази коду, куди попередні автори кидали ints, HRESULTS, std :: string, char *, випадкові класи ... скрізь різні речі; просто назвіть тип і його, певно, кудись кинули. І взагалі немає загального базового класу. Повірте, все було набагато охайніше, коли ми дійшли до того, що всі кинуті типи мали спільну базу, яку ми могли зловити і не знаємо, що нічого не пройде. Тож будь ласка, зробіть собі (і тим, хто повинен буде підтримувати ваш код у майбутньому) послугу і зробіть це так з самого початку.


12

Вам слід успадкувати від boost :: виключення . Він надає набагато більше функцій і добре зрозумілі способи перенесення додаткових даних ... звичайно, якщо ви не використовуєте Boost , ігноруйте цю пропозицію.


5
Однак зауважте, що boost :: виключення не походить від std :: виключення. Навіть виходячи з boost :: виключення, ви також повинні виводити зі std :: виключення (як окрема примітка, коли виходячи з типів винятків, слід використовувати віртуальне успадкування.)
Emil

10

Так, ви повинні виходити з цього std::exception.

Інші відповіли, що std::exceptionпроблема полягає в тому, що ви не можете передати текстове повідомлення, проте, як правило, не годиться намагатись відформатувати повідомлення користувача у момент кидка. Натомість використовуйте об’єкт виключення, щоб перенести всю релевантну інформацію на сайт лову, який потім може відформатувати зручне для користувача повідомлення.


6
+1, хороший бал. Як з розгляду проблем, так і з точки зору i18n, безумовно, краще дозволити шару презентації побудувати повідомлення користувача.
Джон М Гант

@JohnMGant та Emil: Цікаво, чи можете ви вказати на конкретний приклад того, як це можна зробити. Я розумію, що можна отримати std::exceptionінформацію про виняток і перенести її. Але хто буде відповідати за створення / форматування повідомлення про помилку? ::what()Функція або що - то ще?
alfC

1
Ви відформатуєте повідомлення на сайті вилову, швидше за все, на рівні програми (а не бібліотеки). Ви ловите його за типом, потім досліджуєте його для інформації, а потім локалізуєте / форматуєте відповідне повідомлення користувача.
Еміль

9

Причина, з якої ви можете успадкувати, std::exceptionполягає в тому, що це дозволяє скинути виняток, який потрапляє відповідно до цього класу, тобто:

class myException : public std::exception { ... };
try {
    ...
    throw myException();
}
catch (std::exception &theException) {
    ...
}

6

Є одна проблема з успадкуванням, про яку слід знати, - це нарізка об'єктів. Коли ви пишете throw e;вираз "кидок", ініціалізує тимчасовий об'єкт, який називається об'єктом виключення, тип якого визначається шляхом видалення будь-яких cv-кваліфікаторів верхнього рівня зі статичного типу операнду throw. Це може бути не те, чого ви очікуєте. Приклад проблеми ви можете знайти тут .

Це не аргумент проти спадкування, це просто інформація, яка повинна «знати».


3
Я думаю, що відібрати це те, що "кинути е;" зло, і "кинути;" гаразд.
Джеймс Шек

2
Так, throw;це нормально, але не очевидно, що ви повинні написати щось подібне.
Кирило В. Лядвінський

1
Особливо болісно для розробників Java, де повторне відкидання проводиться за допомогою "кинути e;"
Джеймс Шек

Розглянемо лише походження абстрактних базових типів. Ловля абстрактного базового типу за значенням призведе до помилки (уникаючи нарізки); ловити абстрактний базовий тип за посиланням, а потім намагатись перекинути його копію, ви отримаєте помилку (знову уникаючи нарізання.)
Еміль

Ви також можете вирішити цю проблему шляхом додавання функції - члена, raise()як така: virtual void raise() { throw *this; }. Просто пам’ятайте, що це перекриє в кожному похідному винятку.
Час Джастіна - відновіть Моніку

5

Різниця: std :: runtime_error vs std :: виключення ()

Чи слід вам успадковувати її чи ні, не залежить від вас. Стандарт std::exceptionта його стандартні нащадки пропонують одну можливу структуру ієрархії винятків (поділ на logic_errorпідгірархію та runtime_errorсубгіерархію) та один можливий інтерфейс об'єкта винятку. Якщо вам подобається - використовуйте. Якщо вам чомусь потрібно щось інше - визначте власні рамки виключень.


3

Оскільки мова вже видає std :: виняток, вам потрібно все-таки виловити її, щоб забезпечити гідне повідомлення про помилки. Ви можете також використовувати той самий улов для всіх власних несподіваних винятків. Крім того, майже будь-яка бібліотека, яка видає винятки, отримує їх від std :: виключення.

Іншими словами, це теж є

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

або

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

І другий варіант, безумовно, кращий.


3

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

Після того, як ви захопили виняток, ви можете скористатися цим whatметодом для отримання додаткової інформації. C ++ не підтримує введення качок, тому інший клас із whatметодом потребує іншого вилову та іншого коду для його використання.


7
не підклас std :: виключення, а потім catch (std :: виключення e). Вам потрібно ввести посилання на std :: виключення & (або вказівник) або ж ви зрізаєте дані підкласу.
jmucchiello

1
Тепер це була німа помилка - звичайно, я знаю краще. stackoverflow.com/questions/1095225/…
Mark Ransom

3

Походити з будь-якого типового винятку чи ні, це перше питання. Це дає можливість обробляти єдиний виняток для всіх стандартних винятків бібліотеки та ваших власних, але це також заохочує такі обробляючі файли. Проблема полягає в тому, що слід ловити лише винятки, з якими можна впоратися. Наприклад, main (), наприклад, ловити всі std :: винятки, ймовірно, добре, якщо рядок what () буде записаний як крайній спосіб перед виходом. В іншому випадку це навряд чи буде хорошою ідеєю.

Після того, як ви вирішили виходити зі стандартного типу винятку чи ні, тоді питання полягає в тому, яка повинна бути основою. Якщо вашій програмі не потрібен i18n, ви можете подумати, що форматування повідомлення на сайті виклику - це так само добре, як збереження інформації та генерування повідомлення на сайті виклику. Проблема полягає в тому, що відформатоване повідомлення може не знадобитися. Краще використовувати ліниву схему генерації повідомлень - можливо, з попередньо виділеною пам'яттю. Потім, якщо повідомлення потрібне, воно буде генеруватися при доступі (і, можливо, кешоване в об’єкт винятку). Таким чином, якщо повідомлення генерується при перекиданні, тоді похідне std :: виключення, як std :: runtime_error, потрібне як базовий клас. Якщо повідомлення генерується ліниво, то відповідною базою є std :: виключення.


0

Ще одна причина винятків підкласу - кращий аспект дизайну при роботі над великими інкапсульованими системами. Ви можете використовувати його повторно для таких речей, як повідомлення для перевірки, запити користувачів, фатальні помилки контролера тощо. Замість того, щоб переписувати або переробляти всі валідації, як повідомлення, ви можете просто "зафіксувати" це в головному вихідному файлі, але викинете помилку де завгодно у всьому наборі класів.

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

Це робити також означає, що ви можете повторно використовувати ті самі класи, але на різних інтерфейсах. наприклад, програма Windows може використовувати поле для повідомлень, веб-служба покаже html, а система звітності записуватиме її тощо.


0

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

Використовуйте std::nested_exceptionіstd::throw_with_nested

Тут і тут описано на StackOverflow , як ви можете отримати зворотний зв'язок за своїми винятками всередині свого коду без необхідності налагодження чи громіздкого журналу, просто написавши належний обробник винятків, який повторно скине вкладені винятки.

Оскільки ви можете це зробити з будь-яким похідним класом винятків, ви можете додати багато інформації до такого зворотного треку! Ви також можете поглянути на мій MWE на GitHub , де backtrace виглядатиме приблизно так:

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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.