Чому специфікації виключень погані?


50

Ще в школі близько 10 років тому вони вчили вас використовувати специфікатори виключень. Оскільки мій досвід є одним із них, програмістів Torvaldish C, який вперто уникає C ++, якщо не змушений до цього, я стикаюся лише на C ++, і коли я все ще використовую специфікатори виключень, оскільки саме цього мене вчили.

Однак більшість програмістів на C ++, схоже, нахмуриться на специфікаторів виключень. Я читав дебати та аргументи від різних гурусів С ++, як-от ці . Наскільки я розумію, це зводиться до трьох речей:

  1. Специфікатори винятку використовують систему типів, що не відповідає решті мови ("тіньова система типу").
  2. Якщо ваша функція за допомогою специфікатора винятків кидає щось інше, крім того, що ви вказали, програма буде припинятися поганими, несподіваними способами.
  3. Специфікатори винятку будуть видалені в майбутньому стандарті C ++.

Я чогось тут пропускаю чи це всі причини?

Моя власна думка:

Щодо 1): Так що. C ++ - це, мабуть, найбільш непослідовна мова програмування, коли-небудь зроблена, синтаксично. У нас є макроси, goto / мітки, орда (сховище?) Не визначеного / не визначеного / визначеного поведінки поведінки, неправильно визначені цілі типи, всі неявні правила просування типу, ключові слова у спеціальному випадку, наприклад, товариш, авто , реєструвати, явно ... І так далі. Хтось, мабуть, міг би написати кілька товстих книг про все дивне в C / C ++. То чому люди реагують на цю особливу непослідовність, що є незначною вадою порівняно з багатьма іншими набагато небезпечнішими рисами мови?

Щодо 2): Це не моя власна відповідальність? Є так багато інших способів, як я можу написати фатальну помилку на C ++, чому саме цей випадок гірший? Замість того, щоб писати, throw(int)а потім кидати Crash_t, я можу також стверджувати, що моя функція повертає вказівник на int, потім робить дикий, явний typecast і повертає вказівник на Crash_t. Дух C / C ++ завжди полягав у тому, щоб залишити більшу частину відповідальності на програміста.

А як щодо переваг тоді? Найбільш очевидним є те, що якщо ваша функція намагається явно викинути будь-який тип, крім того, що ви вказали, компілятор видасть вам помилку. Я вважаю, що стандарт щодо цього зрозумілий (?). Помилки трапляються лише тоді, коли ваша функція викликає інші функції, які, у свою чергу, кидають неправильний тип.

Виходячи зі світу детермінованих, вбудованих програм на С, я, безумовно, вважаю за краще точно знати, яка функція кине на мене. Якщо в мові є щось, що це підтверджує, чому б не використати це? Альтернативи, здається, такі:

void func() throw(Egg_t);

і

void func(); // This function throws an Egg_t

Я думаю, є велика ймовірність того, що абонент ігнорує / забуде застосувати пробний лов у другому випадку, рідше в першому випадку.

Як я розумію, якщо будь-яка з цих двох форм вирішить раптово кинути інший виняток, програма вийде з ладу. У першому випадку тому, що не можна кидати інший виняток, у другому - тому, що ніхто не очікував, що він кине іспанську інквізицію_t, і тому цей вираз не потрапляє там, де мав би бути.

У випадку останнього, щоб випадок кращої можливості (...) на найвищому рівні програми насправді не виглядав нічим кращим, ніж програмний збій: "Гей, десь у вашій програмі щось викинуло дивний, незроблений виняток . ". Ви не можете відновити програму, коли ви знаходитесь далеко від місця, де було викинуто виняток, єдине, що ви можете зробити - це вийти з програми.

І з точки зору користувача, вони могли не менше турбуватися, якщо вони отримають злісне поле від ОС, що говорить "Програма припинена. Blablabla за адресою 0x12345" або поле злого повідомлення з вашої програми, що говорить "Невиправлений виняток: myclass. func.something ». Клоп все ще є.


З майбутнім стандартом C ++ у мене не буде іншого варіанту, крім відмови від специфікаторів винятку. Але я б швидше почув якийсь твердий аргумент, чому вони погані, а не "Його Святість заявив це, і тому це так". Можливо, проти них є більше аргументів, ніж тих, що я перелічив, чи, можливо, є їх більше, ніж я усвідомлюю?


30
Мені спокуса сприйняти це як "скандал, замаскований під питання". Ви запитуєте три дійсні моменти щодо E.specs, але чому ви турбуєте нас своїми C ++ - це так дратує і як я схожу на С краще?
Мартін Ба

10
@Martin: Тому що я хочу зазначити, що я обоє упереджений і не в курсі всіх деталей, а також, що я розглядаю мову з наївними та / або ще не зіпсованими очима. Але також, що C і C ++ - це вже неймовірно недосконалі мови, тож одна вада більш-менш насправді не має значення. Повідомлення насправді було набагато гірше, перш ніж я його відредагував :) Аргументи проти специфікаторів винятків також досить суб'єктивні, і тому важко їх обговорити, не отримуючи суб'єктивного характеру.

SpanishInquisition_t! Веселий! Мене особисто вразило використання покажчика кадру стека для викидів винятків і, здається, це може зробити код набагато чистішим. Однак я ніколи ніколи не писав код за винятком. Назвіть мене старомодно, але повернені значення для мене працюють чудово.
Шахбаз

@Shahbaz Як можна прочитати між рядками, я теж досить старомодний, але все ще ніколи насправді не ставив під сумнів, чи є винятки самі по собі добрі чи погані.

2
Минулий час кидка буде кинув , що не кинув . Це сильне дієслово.
TRiG

Відповіді:


48

Характеристики винятків погані, тому що вони слабко застосовуються, а отже, насправді не досягають великої кількості, а також погані, оскільки змушують час виконання перевіряти на несподівані винятки, щоб вони могли припинити (), а не викликати UB , це може втратити значну кількість продуктивності.

Отже, підсумовуючи, специфікації винятків не виконуються досить сильно на мові, щоб зробити код будь-яким безпечнішим, а реалізація їх, як зазначено, була великою втратою продуктивності.


2
Але перевірка часу виконання не є виною специфікації виключень як такої, а скоріше з тієї частини стандарту, яка говорить про те, що має бути припинено, якщо неправильний тип викинуто. Чи не було б розумнішим просто зняти вимогу, що веде до цієї динамічної перевірки, і нехай все буде статично оцінено компілятором? Здається, RTTI є справжнім винуватцем тут, чи ...?

7
@Lundin: неможливо статично визначити всі винятки, які можуть бути викинуті з функції в C ++, оскільки мова дозволяє довільно і недетерміновано змінювати керуючий потік під час виконання (наприклад, через покажчики функцій, віртуальні методи та подібні). Реалізація статичної перевірки виключень під час компіляції, як Java, вимагатиме кардинальних змін у мові та безлічі C-сумісності.

3
@OrbWeaver: Я думаю, що статичні перевірки цілком можливі - специфікація винятку буде частиною специфікації функції (наприклад, значення, що повертається), а покажчики функцій, віртуальні зміни та інше повинні мати сумісні характеристики. Основне заперечення полягало б у тому, що це функція «все або нічого» - функції зі статичними специфікаціями виключень не можуть викликати застарілі функції. (Виклик функцій C було б добре, оскільки вони нічого не можуть кинути).
Майк Сеймур

2
@Lundin: Характеристики винятків не видалено, вони просто застаріли. Спадковий код, який використовує їх, все ще буде чинним.
Майк Сеймур

1
@Lundin: Старі версії C ++ за винятком специфікацій цілком сумісні. Вони не відмовляться від компіляції або несподівано, якщо код був дійсним раніше.
DeadMG

18

Однією з причин, що їх ніхто не використовує, є те, що ваше основне припущення неправильне:

"Найбільш очевидна [перевага] полягає в тому, що якщо ваша функція намагається явно викинути будь-який тип, окрім того, що ви вказали, компілятор видасть вам помилку."

struct foo {};
struct bar {};

struct test
{
    void baz() throw(foo)
    {
        throw bar();
    }
};

int main()
{
    test x;
    try { x.baz(); } catch(bar &b) {}
}

Ця програма компілюється без помилок чи попереджень .

Крім того, навіть якщо виняток був би спійманий , програма все ще припиняється.


Редагувати: щоб відповісти на питання у вашому запитанні, catch (...) (або краще, catch (std :: exeption &) або інший базовий клас, а потім catch (...)) все ще корисно, навіть якщо ви цього не зробите точно знаю, що пішло не так.

Подумайте, що ваш користувач натиснув кнопку меню для "збереження". Обробник кнопки меню пропонує програмі зберегти. Це може вийти з ладу з безлічі причин: файл знаходився на мережевому ресурсі, який зник, був файл лише для читання, і його не вдалося зберегти і т. Д. Але обробник не дуже переймається, чому щось не вдалося; байдуже лише те, що це або вдалося, або не вдалося. Якщо це вдалося, все добре. Якщо це не вдалося, це може повідомити користувачеві. Точний характер винятку не має значення. Крім того, якщо ви пишете правильний, безпечний для винятків код, це означає, що будь-яка така помилка може поширюватися через вашу систему, не знижуючи її - навіть для коду, який не хвилює помилку. Це полегшує розширення системи. Наприклад, ви економите тепер через з'єднання з базою даних.


4
@Lundin він склав без попереджень. І ні, ви не можете. Не надійно. Ви можете зателефонувати за допомогою функціональних покажчиків, або це може бути функція віртуального члена, і похідний клас викидає производний_випуск (про який базовий клас можливо не може знати).
Kaz Dragon

Жорстка удача. Embarcadero / Borland подає попередження про це: "виняток кидання порушує специфікатор винятку". Мабуть, GCC не робить. Немає жодної причини, хоча вони не могли виконати попередження. І аналогічно, інструмент статичного аналізу може легко знайти цю помилку.

14
@Lundin: Будь ласка, не починайте сперечатися та повторювати неправдиву інформацію. Ваше запитання вже було чимось розлюченим, і стверджувати, що неправдиві речі не допомагають. Засіб статичного аналізу НЕ МОЖЕ знайти, якщо це трапиться; це може означати вирішення проблеми зупинки. Більше того, потрібно було б проаналізувати всю програму, і дуже часто все джерело недоступне.
Девід Торнлі

1
@Lundin той самий інструмент статичного аналізу може сказати вам, які викинуті винятки залишають ваш стек, тому в цьому випадку використання специфікацій винятків все одно не купує вам нічого, крім потенційного помилково-негативного, який призведе до того, що ваша програма зможе обробити випадок помилок у набагато приємніший спосіб (наприклад, оголосити про невдачу та продовжувати працювати: див. приклад другої половини моєї відповіді).
Kaz Dragon

1
@Lundin Добре запитання. Відповідь полягає в тому, що, як правило, ви не знаєте, чи будуть виклики функцій викидати винятки, тому безпечне припущення - це все. Де їх наздогнати - це питання, на яке я відповів у stackoverflow.com/questions/4025667/… . Зокрема, обробники подій утворюють природні межі модулів. Якщо дозволити виняткам потрапляти в загальну систему вікон / подій (наприклад), буде покарано. Обробник повинен їх спіймати, якщо програма виживе там.
Kaz Dragon

17

Це інтерв'ю з Андерсом Хейльсбергом досить відоме. У ньому він пояснює, чому команда дизайнерів C # в першу чергу відкинула перевірені винятки. У двох словах, це дві основні причини: спроможність та масштабованість.

Я знаю, що ОП фокусується на C ++, тоді як Хейльсберг обговорює C #, але моменти, які робить Хейльсберг, також цілком застосовні і до C ++.


5
"Точки, які робить Хейльсберг, також чудово застосовні і для C ++" .. Неправильно! Перевірені винятки , реалізовані в Java, змушують абонента мати справу з ними (та / або абонента абонента тощо). Характеристики винятків у C ++ (у той час як досить слабкі та марні) застосовуються лише до однієї "межі функції-виклику" і не "поширюють стек викликів", як це робиться перевіреними винятками.
Мартін Ба

3
@Martin: Я погоджуюся з вами щодо поведінки специфікацій винятків у C ++. Але моя фраза, яку ви цитуєте "пункти, які робить Хейльсберг, цілком застосовна і для C ++", стосується, ну, пунктів, які робить Хейльсберг, а не специфікацій C ++. Іншими словами, я говорю тут про простоту та масштабованість програми, а не про поширення виключень.
CesarGon

3
Я не погодився з Хейлсбергом досить рано: "Занепокоєння, яке я маю щодо перевірених винятків, - це наручники, які вони надягають на програмістів. Ви бачите програмістів, що підбирають нові API, у яких є всі ці пункти кидок, і тоді ви бачите, як збивається їхній код, і ви розумієте перевірені винятки їм нічим не допомагають. Це подібні дизайнери диктаторських API, які розповідають, як зробити обробку ваших винятків. " --- Винятки перевіряються чи ні, вам все одно доведеться обробляти винятки, викинуті API, який ви викликаєте. Перевірені винятки просто роблять це явним.
Quant_dev

5
@quant_dev: Ні, вам не доведеться обробляти винятки, викинуті API, який ви викликаєте. Ідеально правильним варіантом є не обробляти винятки, а дозволити їм підсилювати стек викликів, щоб ваш абонент обробляв.
CesarGon

4
@CesarGon Якщо не обробляти винятки, це також зробити вибір, як з ними поводитися.
Quant_dev
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.