Введіть ключове слово в підпис функції


200

Яка технічна причина, чому вважається поганою практикою використання throwключового слова C ++ у підписі функції?

bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}

Дивіться цей недавній пов'язаний з цим питання: stackoverflow.com/questions/1037575 / ...
laalto


1
Чи noexceptщось змінюється?
Аарон Макдейд

12
Немає нічого поганого в тому, щоб мати думки про щось, що стосується програмування. Цей критерій закриття поганий, принаймні для цього питання. Це цікаве запитання з цікавими відповідями.
Anders Lindén

Слід зазначити, що специфікації винятків застаріли, оскільки C ++ 11.
Франсуа Андріо

Відповіді:


127

Ні, це не вважається хорошою практикою. Навпаки, це взагалі вважається поганою ідеєю.

http://www.gotw.ca/publications/mill22.htm детальніше розповідає про те, чому, але проблема полягає в тому, що компілятор не в змозі цього застосувати, тому його потрібно перевірити під час виконання, що зазвичай небажаний. І це не добре підтримується в будь-якому випадку. (MSVC ігнорує специфікації винятків, за винятком cast (), які він трактує як гарантію того, що жоден виняток не буде викинутий.


25
Так. Є кращі способи додати пробіл до коду, ніж кидати (myEx).
Assaf Lavie

4
так, люди, які просто виявляють специфікації винятків, часто припускають, що вони працюють як у Java, де компілятор може їх застосувати. У C ++ цього не станеться, що робить їх набагато менш корисними.
jalf

7
Але як щодо документальної мети тоді? Крім того, вам скажуть, які винятки ви ніколи не спіймаєте, навіть якщо спробуєте.
Anders Lindén

1
@ AndersLindén з якою документальною метою? Якщо ви просто хочете задокументувати поведінку свого коду, просто поставте коментар над ним. Не впевнений, що ви маєте на увазі під другою частиною. Винятки, які ви ніколи не зловите, навіть якщо спробуєте?
jalf

4
Я думаю, що код є потужним, коли справа стосується документування вашого коду. (коментарі можуть брехати). "Документальний" ефект, про який я маю на увазі, полягає в тому, що ви точно знатимете, які винятки ви можете зловити, а інших - не бути.
Андерс Лінден

57

Jalf вже пов’язаний з ним, але GOTW висловлює це досить добре, чому специфікації виключень не такі корисні, як можна сподіватися:

int Gunc() throw();    // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)

Чи правильні коментарі? Не зовсім. Gunc()може справді кинути щось, а Hunc()може й кинути щось, крім А чи Б! Компілятор просто гарантує безглуздість їх обіграти, якщо вони ... о, і більшу частину часу також бити програму безглуздо.

Ось саме це і зводиться, ви, ймовірно, просто закінчитеся закликом, terminate()і ваша програма помер швидко, але болісно.

Висновок GOTW:

Ось ось, що, як видається, є найкращою порадою, яку ми, як громада, дізналися сьогодні:

  • Мораль №1: Ніколи не пишіть специфікацію виключень.
  • Мораль №2: За винятком порожнього, але якби я був ти, я б цього навіть уникав.

1
Я не впевнений, чому б я кинув виняток і не зміг би його згадати. Навіть якщо його кидає інша функція, я знаю, які винятки можна кинути. Єдину причину, яку я бачу, це те, що це нудно.
MasterMastic

3
@Ken: Справа в тому, що написання специфікацій виключень має переважно негативні наслідки. Єдиний позитивний ефект полягає в тому, що він показує програмісту, які винятки можуть статися, але оскільки компілятор не перевіряється розумним чином, він схильний до помилок і тому не варто багато чого.
sth

1
Добре, дякую за відгук. Я думаю, саме для цього потрібна документація.
MasterMastic

2
Неправильно. Необхідно написати специфікацію винятку, але ідея полягає в тому, щоб повідомити про те, які помилки абонент повинен спробувати виправити.
HelloWorld

1
Так само, як говорить @StudentT: відповідальність за функцію - гарантувати, що не кидатиме більше ніяких інших винятків. Якщо вони є, програма припиняється як слід. Заявити кидок означає, що я не відповідальність за вирішення цієї ситуації, і абонент повинен мати достатньо інформації для цього. Якщо не декларувати винятки, це означає, що вони можуть виникнути в будь-якому місці, і з ними можна працювати де завгодно. Це, безумовно, боротьба з ООП. Це невдача дизайну для вилучення виключень у неправильних місцях. Я б рекомендував не кидати порожнє, оскільки винятки є винятковими і більшість функцій все-таки повинні викидати порожні.
Jan Turoň

30

Щоб додати трохи більше значення всім іншим відповідям на це запитання, варто вкласти кілька хвилин у запитання: Який результат є наступним кодом?

#include <iostream>
void throw_exception() throw(const char *)
{
    throw 10;
}
void my_unexpected(){
    std::cout << "well - this was unexpected" << std::endl;
}
int main(int argc, char **argv){
    std::set_unexpected(my_unexpected);
    try{
        throw_exception();
    }catch(int x){
        std::cout << "catch int: " << x << std::endl;
    }catch(...){
        std::cout << "catch ..." << std::endl;
    }
}

Відповідь: Як зазначалося тут , програма дзвонить, std::terminate()і тому ніхто з обробників винятків не буде викликаний.

Деталі: Перша my_unexpected()функція викликається, але оскільки вона не перекидає відповідний тип виключення для throw_exception()прототипу функції, врешті-решт, std::terminate()викликається. Отже, повний вихід виглядає приблизно так:

user @ user: ~ / tmp $ g ++ -o крім.test крім.test.cpp
user @ user: ~ / tmp $ ./except.test
добре - це було несподівано
припинено після викидання екземпляра "int"
Aborted (core скинутий)


12

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

Для документування винятків, які може кинути функція, я зазвичай роблю це:

bool
some_func() /* throw (myExc) */ {
}

5
Також корисно зауважити, що виклик на std :: unknown () зазвичай призводить до виклику std :: terminate () та різкого завершення вашої програми.
sth

1
- і що MSVC принаймні не реалізує цю поведінку, наскільки я знаю.
jalf

9

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

Що стосується C ++, моє загальне правило - використовувати лише специфікації метання, щоб вказати, що метод не може кинути. Це сильна гарантія. Інакше припустимо, що це може кинути що завгодно.


9

Щоправда, гуглюючи щодо специфікації цього кидка, я переглянув цю статтю: - ( http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx )

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

   class MyClass
   {
    size_t CalculateFoo()
    {
        :
        :
    };
    size_t MethodThatCannotThrow() throw()
    {
        return 100;
    };
    void ExampleMethod()
    {
        size_t foo, bar;
        try
        {
            foo = CalculateFoo();
            bar = foo * 100;
            MethodThatCannotThrow();
            printf("bar is %d", bar);
        }
        catch (...)
        {
        }
    }
};

Коли компілятор бачить це, за допомогою атрибута "cast ()" компілятор може повністю оптимізувати змінну "bar" далеко, оскільки він знає, що немає можливості викинути виняток з MethodThatCannotThrow (). Без атрибута return () компілятор повинен створити змінну "bar", тому що якщо MethodThatCannotThrow видає виняток, обробник винятків може / залежатиме від значення змінної бар.

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


3

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

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

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

Цитата з наведеного вище посилання для тих, хто поспішає (містить приклад поганих ненавмисних ефектів вказівки накидання на вбудовану функцію від наївного компілятора):

Обґрунтування специфікацій винятків

Специфікації винятків [ISO 15.4] іноді кодуються, щоб вказати на те, які винятки можуть бути кинуті, або тому, що програміст сподівається, що вони покращать продуктивність. Але розглянемо наступного члена із розумного вказівника:

T & operator * () const кінуць () {return * ptr; }

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

Наприклад, деякі компілятори відключають вбудовану лінію, якщо є специфікація винятків. Деякі компілятори додають блоки "try / catch". Такі песимізації можуть стати катастрофою, що робить код непридатним у практичних програмах.

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

Функція, що не вбудовується, - це місце, де специфікація винятків "не кидає нічого" може мати певну користь у деяких компіляторів.


1
"Найбільша проблема із специфікаціями винятків полягає в тому, що програмісти використовують їх так, ніби вони мають той ефект, який хотів би програміст, замість ефекту, який вони насправді мають." Це причина №1 для помилок при спілкуванні з машинами або людьми: різниця між тим, що ми говоримо, і тим, що ми маємо на увазі.
Тагомізер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.