Чи варто використовувати специфікатор винятку в C ++?


123

У C ++ ви можете вказати, що функція може або не може викидати виняток, використовуючи специфікатор винятку. Наприклад:

void foo() throw(); // guaranteed not to throw an exception
void bar() throw(int); // may throw an exception of type int
void baz() throw(...); // may throw an exception of some unspecified type

Я сумніваюся, що насправді їх використовувати через наступне:

  1. Компілятор насправді не застосовує специфікаторів винятку будь-яким суворим способом, тому переваги не великі. В ідеалі ви хочете отримати помилку компіляції.
  2. Якщо функція порушує специфікатор винятку, я думаю, що стандартна поведінка полягає у припиненні програми.
  3. У VS.Net він розглядає кидок (X) як кидок (...), тому дотримання стандарту не є сильним.

Як ви вважаєте, слід використовувати специфікатори винятків?
Будь ласка, дайте відповідь "так" чи "ні" та вкажіть деякі причини, щоб обґрунтувати свою відповідь.


7
"кинути (...)" не є стандартом C ++. Я вважаю, що це розширення в деяких компіляторах і, як правило, має те саме значення, що і специфікація не виняток.
Річард Корден

Відповіді:


97

Немає.

Ось кілька прикладів, чому:

  1. Код шаблону неможливо записати за винятком специфікацій,

    template<class T>
    void f( T k )
    {
         T x( k );
         x.x();
    }

    Копії можуть кидатись, параметр, що передається, може кидати і x()може невідомий виняток.

  2. Характеристики виключень, як правило, забороняють розширюваність.

    virtual void open() throw( FileNotFound );

    може перерости в

    virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );

    Ви справді могли це записати як

    throw( ... )

    Перше не розширюється, друге - надмірно, а третє - це справді те, що ви маєте на увазі, коли ви пишете віртуальні функції.

  3. Спадковий код

    Коли ви пишете код, який спирається на іншу бібліотеку, ви насправді не знаєте, що це може зробити, коли щось жахливо піде не так.

    int lib_f();
    
    void g() throw( k_too_small_exception )
    { 
       int k = lib_f();
       if( k < 0 ) throw k_too_small_exception();
    }

    gприпиниться, коли lib_f()кидає. Це (в більшості випадків) не те, що ви насправді хочете. std::terminate()ніколи не слід називати. Завжди краще дозволити програмі вийти з ладу за нездійсненим винятком, з якого ви зможете знайти слід стека, ніж мовчки / насильно померти.

  4. Напишіть код, який повертає поширені помилки та викидає у виняткових випадках.

    Error e = open( "bla.txt" );
    if( e == FileNotFound )
        MessageUser( "File bla.txt not found" );
    if( e == AccessDenied )
        MessageUser( "Failed to open bla.txt, because we don't have read rights ..." );
    if( e != Success )
        MessageUser( "Failed due to some other error, error code = " + itoa( e ) );
    
    try
    {
       std::vector<TObj> k( 1000 );
       // ...
    }
    catch( const bad_alloc& b )
    { 
       MessageUser( "out of memory, exiting process" );
       throw;
    }

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


1
у 3, технічно було б std :: несподівано не std :: припинити. Але коли ця функція викликана, за замовчуванням викликається abort (). Це генерує основний дамп. Як це гірше, ніж необроблений виняток? (що в основному те саме)
Грег Роджерс

6
@Greg Роджерс: Невиконаний виняток все ще робить розмотування стека. Це означає, що будуть викликані деструктори. І в цих деструкторах можна зробити багато чого, наприклад: Ресурси правильно звільнені, журнали правильно написані, інші процеси будуть розказані про те, що поточний процес виходить з ладу тощо. Підводячи підсумок, це RAII.
paercebal

Ви залишили без уваги: ​​Це ефективно обертає все в try {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ]конструкції, незалежно від того, чи хочете ви пробувати блок спробувати.
Девід Торнлі

4
@paercebal Це неправильно. Це визначається реалізацією того, чи руйнуються деструктори за невиконані винятки. У більшості середовищ не знімаються деструктори стека / запуску, якщо виняток не спійманий. Якщо ви хочете переконатися, що ваші деструктори працюватимуть навіть тоді, коли виняток викидається та не обробляється (це має сумнівне значення), вам потрібно написати свій код на кшталтtry { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }
Logan Capaldo

" Завжди краще дозволити програмі вийти з ладу за нездійсненим винятком, з якого ви зможете знайти слід стека, ніж мовчки / насильно померти ". Як "крах" може бути кращим, ніж чистий дзвінок terminate()? Чому б просто не зателефонувати abort()?
curiousguy

42

Уникайте специфікацій виключень у C ++. Причини, які ви наводите у своєму питанні, є досить гарним початком того, чому.

Див. "Прагматичний погляд на технічні характеристики винятку " Герба Саттера .


3
@awoodland: використання "динамічних винятків-специфікацій" ( throw(optional-type-id-list)) застаріло в C ++ 11. Вони досі перебувають у стандарті, але, мабуть, надіслано попереджувальний постріл про те, що їх використання слід ретельно розглянути. C ++ 11 додає noexceptспецифікацію та оператора. Я не знаю достатньо деталей, noexceptщоб прокоментувати це. Ця стаття видається досить детальною: akrzemi1.wordpress.com/2011/06/10/using-noexcept І у Дітмара Кюля є стаття в журналі про перевантаження в червні 2011 року: accu.org/var/uploads/journals/overload103.pdf
Майкл Берр

@MichaelBurr Тільки throw(something)вважається марною і поганою ідеєю. throw()корисно.
curiousguy

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

14

Я думаю, що стандартно, за винятком умовних (для C ++)
специфікаторів винятку, експеримент із стандартом C ++, який здебільшого не вдався.
Винятком є ​​те, що специфікатор no кидання корисний, але вам слід також внутрішньо додати відповідний блок спробувати catch, щоб переконатися, що код відповідає специфікатору. У Трави Саттера є сторінка на цю тему. Gotch 82

Крім того, я думаю, що варто описати гарантії виключення.

Це в основному документація про те, як на стан об'єкта впливають винятки, що уникають методу на цьому об'єкті. На жаль, вони не застосовуються або іншим чином згадуються компілятором.
Підвищення та винятки

Гарантії винятку

Ніяких гарантій:

Немає гарантії щодо стану об'єкта після виходу винятком із методу.
У цих ситуаціях об'єкт більше не повинен використовуватися.

Основна гарантія:

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

Сильна гарантія: (також транзакційна гарантія)

Це гарантує, що метод буде завершено успішно.
Або буде викинуто Виняток і стан об'єктів не зміниться.

Гарантія відкидання:

Метод гарантує, що з цього методу не допускається жодних винятків.
Усі деструктори повинні зробити цю гарантію.
| Примітка: Якщо виняток уникає деструктора, тоді як виняток вже розповсюджується
| додаток припиняється


Гарантії - це те, що повинен знати кожен програміст на C ++, але, здається, вони не пов'язані зі специфікаціями виключень.
Девід Торнлі

1
@David Thornley: Я бачу гарантії як те, якими повинні були бути специфікації виключень (тобто метод із сильним G не може називати метод із базовим G без захисту). На жаль, я не впевнений, що вони визначені достатньо добре, щоб бути застосованим корисним чином бути компілятором.
Мартін Йорк

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

@curiousguy. Так робить Java, тому що перевірки виконуються під час компіляції. C ++ - це перевірки часу виконання. Отже: Guarantee that functions will only throw listed exceptions (possibly none). Неправда. Це лише гарантує, що якщо функція кине ці винятки, програма припиниться. Enable compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrownНе можу цього зробити. Тому що я лише зазначив, що ви не можете гарантувати, що виняток не буде кинутий.
Мартін Йорк

1
@LokiAstari "Ось так це робить Java, оскільки перевірки виконуються під час компіляції_" Специфікація винятків Java - це експеримент, який не вдався. У Java немає найкориснішої специфікації виключень: throw()(не кидає). " Це лише гарантує, що якщо функція кине ці винятки, додаток припиняється " Ні "не відповідає" означає істину. У C ++ немає гарантії, що функція не викликає terminate()ніколи. " Тому що я лише зазначив, що ви не можете гарантувати, що виняток не буде кинуто " Ви гарантуєте, що функція не буде кидатися. Це саме те, що вам потрібно.
curiousguy

8

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


7

Єдиний корисний специфікатор винятку - "кинути ()", як у "не кидає".


2
Чи можете ви, будь ласка, додати причину, чому це корисно?
buti-oxa

2
чому це не корисно? немає нічого кориснішого, ніж знання якоїсь функції не збирається починати викидати винятки ліворуч праворуч і по центру.
Метт Столяр

1
Будь ласка, дивіться обговорення Herb Sutter, на яке посилається у відповіді Майкла Берра, для детального пояснення.
Гарольд Екстром

4

Технічні характеристики винятків не є надзвичайно корисними інструментами в C ++. Однак, є / є / корисне використання для них, якщо вони поєднуються з std :: несподівано.

Що я роблю в деяких проектах - це код із специфікаціями виключень, а потім викликати set_unexpected () з функцією, яка викине особливий виняток із мого власного дизайну. Цей виняток після побудови отримує зворотний зв'язок (специфічно для платформи) і отримується від std :: bad_exception (щоб дозволити його поширювати за бажанням). Якщо він викликає завершення () виклику, як це зазвичай робиться, зворотний слід друкується тим, що () (а також оригінальним винятком, що його спричинило; не важко знайти це), і тому я отримую інформацію про те, де був мій контракт порушено, наприклад, те, що було кинуто несподіване виключення з бібліотеки.

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


3

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

Інакше мені не здається особливо корисним нічого використовувати, окрім throw()зазначення того, що це не кидає ніяких винятків.


3

Ні. Якщо ви їх використовуєте, і викид, який ви не вказали, не був вказаний ні вашим кодом, ні кодом, який називається вашим кодом, тоді поведінка за замовчуванням полягає в негайному припиненні роботи програми.

Крім того, я вважаю, що їх використання було припинено у поточних проектах стандарту C ++ 0x.


3

Специфікація "кидаю ()" дозволяє компілятору проводити деякі оптимізації під час аналізу потоку коду, якщо він знає, що функція ніколи не викине виключення (або принаймні обіцяє ніколи не викидати виняток). Про це коротко розповідає Ларрі Остерман:

http://blogs.msdn.com/larryosterman/archive/2006/03/22/558390.aspx


2

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

Так, очікувана поведінка невказаного винятку, викинутого з функції із специфікаторами винятків, має викликати термін ().

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


2

Так, якщо ви маєте внутрішню документацію. Або, можливо, написати бібліотеку, якою користуватимуться інші, щоб вони могли розповісти, що відбувається, не звертаючись до документації. Кидання чи не кидання можна вважати частиною API, майже як повернене значення.

Я згоден, вони не дуже корисні для забезпечення правильності стилю Java в компіляторі, але це краще, ніж нічого або випадкові коментарі.


2

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


0

Зі статті:

http://www.boost.org/community/exception_safety.html

"Загальновідомо, що неможливо написати універсальний контейнер, безпечний для винятків". Це твердження часто лунає з посиланням на статтю Тома Каргілла [4], в якій він досліджує проблему безпеки винятків для загального шаблону стека. У своїй статті Каргілл ставить багато корисних питань, але, на жаль, не вдається запропонувати вирішення своєї проблеми. На жаль, його статтю багато хто читав як "доказ" цієї спекуляції. З моменту публікації було багато прикладів безпечних для винятків загальних компонентів, серед яких стандартні бібліотечні контейнери C ++.

І справді я можу придумати способи зробити виняток шаблонів безпечним. Якщо у вас немає контролю над усіма підкласами, у вас все одно можуть виникнути проблеми. Для цього можна створити typedefs у своїх класах, які визначають винятки, викинуті різними класами шаблонів. Ця думка, як і завжди, вирішує цю проблему згодом, а не розробляти її з самого початку, і я думаю, що це справжня перешкода.


-2

Специфікації винятку = сміття, запитайте будь-якого розробника Java старше 30 років


8
Java-програмісти старше 30 років повинні відчувати себе погано, вони не могли впоратися з C, вони не мають приводу використовувати Java.
Метт Столяр
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.