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


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.