У чому сенс норетурну?


189

[dcl.attr.noreturn] надає такий приклад:

[[ noreturn ]] void f() {
    throw "error";
    // OK
}

але я не розумію, в чому сенс [[noreturn]], тому що тип повернення функції вже є void.

Отже, в чому сенс noreturnатрибута? Як це слід використовувати?


1
Що настільки важливого в такому вигляді функціоналу (який, швидше за все, відбудеться один раз при виконанні програми), що заслуговує такої уваги? Хіба це не легко виявити ситуацію?
користувач666412

1
@MrLister Поєднання ОП співвідносить поняття "повернення" та "повернення вартості". З огляду на те, як вони майже завжди використовуються в тандемі, я думаю, що плутанина виправдана.
Сліп Д. Томпсон,

Відповіді:


209

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

Це можуть використовувати компілятори, щоб зробити деякі оптимізації та генерувати кращі попередження. Наприклад, якщо fмає атрибут noreturn, компілятор може попередити вас про g()мертвий код під час написання f(); g();. Точно так же компілятор буде знати , щоб не попередити вас про пропущений повернення заяв після дзвінків f().


5
Що з такою функцією, execveяка не повинна повертатися, але могла ? Чи повинен він мати атрибут noreturn ?
Калріш

22
Ні, це не повинно - якщо є можливість управління потоком повернутися до абонента, він не повинен мати noreturnатрибут. noreturnможе використовуватися лише в тому випадку, якщо ваша функція гарантовано виконує щось, що припиняє програму, перш ніж потік управління може повернутися до абонента - наприклад, тому, що ви викликаєте exit (), abort (), затверджуйте (0) тощо.
RavuAlHemio

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

2
@ sepp2k Cool. Тож повернутись не неможливо, просто ненормально. Це корисно. Ура.
Сліп Д. Томпсон

7
@ SlippD.Thompson ні, повернутися неможливо. Кидати виняток - це не повертатися, тож якщо кожен шлях кидає, то це noreturn. Поводження з цим винятком не те саме, що повернулося. Будь-який код в межах tryпісля дзвінка залишається недоступним, і якщо ні, voidто ніякого присвоєння або використання повернутого значення не відбудеться.
Джон Ханна

63

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


29

Це означає, що функція не буде завершена. Керуючий потік ніколи не звернеться до заяви після виклику f():

void g() {
   f();
   // unreachable:
   std::cout << "No! That's impossible" << std::endl;
}

Інформація може використовуватися компілятором / оптимізатором різними способами. Компілятор може додати попередження про те, що наведений вище код недоступний, і він може змінювати фактичний код g()по-різному, наприклад, підтримуючи продовження.



4
@TemplateRex: компілюйте -Wno-returnі отримаєте попередження. Напевно, не той, кого ви очікували, але, ймовірно, достатньо сказати вам, що компілятор має знання про те, що [[noreturn]]є, і він може ним скористатися. (Я трохи здивований, що -Wunreachable-codeне забив ...)
David Rodríguez - dribeas

3
@TemplateRex: Вибачте -Wmissing-noreturn, попередження передбачає, що аналіз потоку визначив, що std::coutце неможливо. У мене немає достатнього нового gcc під рукою, щоб подивитися на створену збірку, але я би не здивувався, якби заклик operator<<було відхилено
David Rodríguez - dribeas

1
Ось збірний дамп (-S -o - прапори в coliru), дійсно скидає "недосяжний" код. Цікаво, що -O1вже достатньо, щоб скинути цей недосяжний код без [[noreturn]]підказки.
TemplateRex

2
@TemplateRex: весь код знаходиться в одній одиниці перекладу і видимий, тому компілятор може зробити висновок [[noreturn]]із коду. Якби цей блок перекладу мав лише декларацію функції, яка була визначена десь в іншому місці, компілятор не зміг би скинути цей код, оскільки він не знає, що функція не повертається. Саме тут атрибут повинен допомогти компілятору.
David Rodríguez - dribeas

17

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

int f(bool b){
    if (b) {
        return 7;
    } else {
        abort();
    }
 }

Якби abort () не був позначений "noreturn", компілятор, можливо, попередив би про те, що цей код має шлях, де f не повертає ціле число, як очікувалося. Але оскільки позначено abort () немає повернення, він знає, що код правильний.


У всіх інших перелічених прикладах використовуються функції недійсності - як це працює, коли у вас є і директива [[no return]], і тип недійсного повернення? Чи вступає в дію директива [[no return]] лише тоді, коли компілятор готовий попередити про можливість не повертатися та ігнорувати попередження? Наприклад, чи йде компілятор: "Гаразд, тут функція недійсна". * продовжує компілювати * "О, дерьмо, цей код може не повернутися! Якщо я попередитиму користувача?" Продовжуй"
Релі Л.

2
Функція noreturn в моєму прикладі не f (), це abort (). Немає сенсу позначати нерегулярну функцію noreturn. Функція, яка іноді повертає значення, а іноді повертається (хорошим прикладом є execve ()), не може бути позначена нерегулярною.
Nadav Har'El

1
неявний перепад - ще один такий приклад: stackoverflow.com/questions/45129741/…
Чіро Сантіллі郝海东冠状病六四事件法轮功

11

Теоретично кажучи, voidце те, що називається іншими мовамиunit або top. Його логічний еквівалент - True . Будь-яке значення може бути законно передано void(кожен тип є підтипом void). Подумайте про це як про "Всесвіт"; немає жодних операцій, спільних для всіх значень у світі, тому немає дійсних операцій зі значенням типу void. По-іншому, кажучи вам, що щось належить до набору Всесвіту, ви не має жодної інформації - ви це вже знаєте. Отже, звук наступний:

(void)5;
(void)foo(17); // whatever foo(17) does

Але призначення нижче:

void raise();
void f(int y) {
    int x = y!=0 ? 100/y : raise(); // raise() returns void, so what should x be?
    cout << x << endl;
}

[[noreturn]]з іншого боку, називається іноді empty , Nothing, Bottomабо Botі є логічним еквівалентом False . Він взагалі не має значень, і вираз цього типу може бути передано (тобто є підтипом) будь-якого типу. Це порожній набір. Зауважте, що якщо хтось скаже вам, що "значення виразу foo () належить порожньому набору", воно є дуже інформативним - воно говорить вам про те, що цей вираз ніколи не завершить його нормальне виконання; вона перерве, кине або повісить. Це якраз навпаки void.

Отже, наступне не має сенсу (псевдо-С ++, оскільки noreturnне є першокласним типом С ++)

void foo();
(noreturn)5; // obviously a lie; the expression 5 does "return"
(noreturn)foo(); // foo() returns void, and therefore returns

Але завдання нижче є цілком законним, оскільки throwкомпілятор розуміє, що він не повертається:

void f(int y) {
    int x = y!=0 ? 100/y : throw exception();
    cout << x << endl;
}

У ідеальному світі ви можете використати noreturnяк повернене значення для функції, raise()наведеної вище:

noreturn raise() { throw exception(); }
...
int x = y!=0 ? 100/y : raise();

На жаль, C ++ цього не дозволяє, можливо, з практичних причин. Натомість це дає вам можливість використовувати [[ noreturn ]]атрибут, який допомагає керувати оптимізаціями та попередженнями компілятора.


5
Ніщо не може бути приведений до типу voidі voidніколи не має значення trueчи falseабо що - небудь ще.
Чіткіший

5
Коли я кажу true, я маю на увазі не «значення trueтипу bool», а логічний сенс, див. Листування Керрі-Говарда
Елазар

8
Теорія абстрактних типів, яка не відповідає системі типів певної мови, не має значення при обговоренні системи типів цієї мови. Питання, про яке йдеться :-), стосується C ++, а не теорії типів.
Очищення

8
(void)true;цілком справедливий, як підказує відповідь. void(true)це щось зовсім інше, синтаксично. Це спроба створити новий об'єкт типу voidшляхом виклику конструктора з trueаргументом; це не вдається, серед інших причин, тому що voidце не перший клас.
Елазар

2
Так воно і є. Я звик використовувати кастинг типу (значення), а не кастинг стилю.
Очищення
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.