Какой смысл в noreturn?

189

[dcl.attr.noreturn] предоставляет следующий пример:

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

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

Итак, в чем смысл этого noreturnатрибута? Как это должно быть использовано?

BЈовић
источник
1
Что такого важного в такого рода функции (которая, скорее всего, произойдет однажды при выполнении программы), которая заслуживает такого внимания? Разве это не легко обнаруживаемая ситуация?
user666412
1
@MrLister ОП объединяет понятия «возвращение» и «возвращаемое значение». Учитывая, как они почти всегда используются в тандеме, я думаю, что путаница оправдана.
Слипп Д. Томпсон

Ответы:

209

Предполагается, что атрибут noreturn используется для функций, которые не возвращаются вызывающей стороне. Это не означает, что void функции (которые возвращают вызывающей стороне - они просто не возвращают значение), но функции, в которых поток управления не вернется к вызывающей функции после завершения функции (например, функции, которые выходят из приложения, цикл навсегда или выбросить исключения, как в вашем примере).

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

sepp2k
источник
5
Как насчет такой функции, execveкоторая не должна возвращаться, но могла бы ? Должен ли он иметь атрибут noreturn ?
Калриш
22
Нет, это не должно - если есть возможность для потока управления вернуться к вызывающей стороне, он не должен иметь noreturnатрибут. noreturnможет использоваться, только если ваша функция гарантированно сделает что-то, что завершит программу, прежде чем поток управления сможет вернуться к вызывающей стороне - например, потому что вы вызываете exit (), abort (), assert (0) и т. д.
RavuAlHemio
7
@ SlippD.Thompson Если вызов функции noreturn заключен в блок try, любой код из блока catch снова будет считаться достижимым.
sepp2k
2
@ sepp2k Круто. Так что вернуться невозможно, просто ненормально. Это полезно Приветствия.
Слипп Д. Томпсон
7
@ SlippD. Томпсон нет, вернуть невозможно. Бросок исключения не возвращается, так что если каждый путь выбрасывает, то это так noreturn. Обработка этого исключения не совпадает с возвратом. Любой код в tryпосле после вызова все еще недоступен, и если нет, voidто никакого присвоения или использования возвращаемого значения не произойдет.
Джон Ханна
63

noreturnне сообщает компилятору, что функция не возвращает никакого значения. Он сообщает компилятору, что поток управления не вернется к вызывающей стороне . Это позволяет компилятору выполнять различные оптимизации - ему не нужно сохранять и восстанавливать любое изменчивое состояние вокруг вызова, он может мертвым кодом исключать любой код, который в противном случае следовал бы за вызовом, и т. Д.

Стивен Кэнон
источник
29

Это означает, что функция не будет завершена. Поток управления никогда не попадет в оператор после вызова f():

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

Информация может использоваться компилятором / оптимизатором различными способами. Компилятор может добавить предупреждение о том, что приведенный выше код недоступен, и он может модифицировать фактический код g()различными способами, например, для поддержки продолжений.

Дэвид Родригес - дрибеи
источник
3
gcc / clang не дает предупреждений
TemplateRex
4
@TemplateRex: скомпилируйте с, -Wno-returnи вы получите предупреждение. Вероятно, не тот, который вы ожидали, но достаточно сказать, что компилятор знает, что [[noreturn]]есть, и он может воспользоваться этим. (Я немного удивлен, что -Wunreachable-codeне
пнул
3
@TemplateRex: Извините -Wmissing-noreturn, предупреждение подразумевает, что анализ потока определил, что std::coutон недоступен. У меня недостаточно нового gcc, чтобы посмотреть на сгенерированную сборку, но я не удивлюсь, если вызов to operator<<будет отброшен
Дэвид Родригес - dribeas
1
Вот дамп сборки (-S -o - flags in coliru), действительно удаляющий «недоступный» код. Интересно, -O1что уже достаточно, чтобы сбросить этот недоступный код без [[noreturn]]подсказки.
TemplateRex
2
@TemplateRex: весь код находится в одном и том же модуле перевода и является видимым, поэтому компилятор может вывести его [[noreturn]]из кода. Если бы у этого модуля преобразования было только объявление функции, которая была определена где-то еще, компилятор не смог бы удалить этот код, поскольку он не знает, что функция не возвращает. Вот где атрибут должен помочь компилятору.
Дэвид Родригес - dribeas
18

Предыдущие ответы правильно объяснили, что такое noreturn, но не почему он существует. Я не думаю, что «оптимизационные» комментарии являются основной целью: функции, которые не возвращаются, редки и обычно не нуждаются в оптимизации. Скорее, я думаю, что основной смысл норетуры - избегать ложноположительных предупреждений. Например, рассмотрим этот код:

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

Если бы abort () не был помечен как «noreturn», компилятор мог бы предупредить, что у этого кода есть путь, где f не возвращает целое число, как ожидалось. Но поскольку abort () помечен как «нет возврата», он знает, что код верен.

Надав хар'ел
источник
Во всех других перечисленных примерах используются функции void - как это работает, когда у вас есть и директива [[no return]], и тип возврата, отличный от void? Директива [[no return]] вступает в действие только тогда, когда компилятор готов предупредить о возможности не возврата и игнорировать предупреждение? Например, идет ли компилятор: «Хорошо, здесь не пустая функция». * продолжает компиляцию * «О, дерьмо, этот код может не вернуться! Должен ли я предупредить пользователя? *» Не важно, я вижу директиву без возврата. Продолжайте "
Роли Л.
2
Функция noreturn в моем примере это не f (), это abort (). Не имеет смысла отмечать не пустую функцию noreturn. Функция, которая иногда возвращает значение, а иногда возвращает (хорошим примером является execve ()), не может быть помечена как noreturn.
Nadav Har'El
1
implicit-fallthrough является еще одним таким примером: stackoverflow.com/questions/45129741/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
12

Тип теоретически говоря, voidэто то , что называется на других языках unitили top. Его логический эквивалент - Истина . Любое значение может быть законно приведено к 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.

Таким образом, следующее не имеет смысла (псевдо-C ++, так как noreturnэто не первоклассный тип C ++)

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или что - нибудь еще.
яснее
6
Когда я говорю true, я имею в виду не «значение trueтипа bool», а логический смысл, см. Соответствие Карри-Ховарда
Elazar
8
Абстрактная теория типов, которая не соответствует системе типов конкретного языка, не имеет значения при обсуждении системы типов этого языка. Вопрос, о котором идет речь :-), касается C ++, а не теории типов.
яснее
8
(void)true;совершенно правильно, как и предполагает ответ. void(true)это что-то совершенно другое, синтаксически. Это попытка создать новый объект типа void, вызвав конструктор с trueаргументом; это терпит неудачу, среди других причин, потому что voidэто не первый класс.
Elazar
2
Так что, это. Я привык использовать приведение типа (значения), а не приведение к стилю.
яснее