Инициализация ссылки - временная привязка к возвращаемому значению

В статье об инициализации ссылки на cppreference.com (время жизни), там написано:

временная привязка к возвращаемому значению функции в операторе return не расширяется: она уничтожается сразу в конце возвращаемого выражения. Такая функция всегда возвращает висячую ссылку.

В этом отрывке рассматриваются исключения, связанные с продлением срока службы временного объекта путем привязки к нему ссылки. Что они на самом деле имеют в виду? Я думал о чем-то вроде

#include <iostream>

int&& func()
{
    return 42;
}

int main()
{
    int&& foo = func();
    std::cout << foo << std::endl;

    return 0;
}

Таким образом, foo должен ссылаться на временное 42. Согласно выдержке, это должна быть оборванная ссылка, но это печатает 42 вместо некоторого случайного значения, поэтому все работает отлично.

Я уверен, что здесь что-то не так, и был бы признателен, если бы кто-нибудь мог решить мою путаницу.


person Ruperrrt    schedule 28.02.2021    source источник
comment
Разыменование висячей ссылки является неопределенным поведением: это может быть печать 42, очистка жесткого диска или начало ядерной войны.   -  person Meowmere    schedule 01.03.2021
comment
Я не могу воспроизвести это: coliru.stacked-crooked.com/a/7211be5336782f11 Но, ну это просто неопределенное поведение, всякое бывает.   -  person πάντα ῥεῖ    schedule 01.03.2021
comment
Извините, я новичок. Так вот что они на самом деле имели в виду под временной привязкой к возвращаемому значению функции в операторе return? И так получилось, что Visual C++ компилирует его таким образом, что печатается 42?   -  person Ruperrrt    schedule 01.03.2021
comment
Неопределенное поведение @Ruperrrt означает, что нет определения поведения. Это не означает, что поведение должно отличаться от ожиданий (это само по себе было бы своего рода определением). Как правило, компиляторы не стараются специально реагировать на программы с UB , они просто реализуют правила для допустимых программ, а для недопустимых программ чипы падают, как могут.   -  person M.M    schedule 01.03.2021


Ответы (1)


Ваш пример очень хорош, но ваш компилятор — нет.

Временное значение часто представляет собой буквальное значение, возвращаемое функцией значение, а также объект, передаваемый функции с использованием синтаксиса имя_класса (аргументы_конструктора). Например, до того, как лямбда-выражения были введены в C++, для сортировки вещей нужно было определить некоторую структуру X с перегруженным operator(), а затем сделать такой вызов:

std::sort(v.begin(), v.end(), X());

В этом случае вы ожидаете, что время жизни временного объекта, созданного с помощью X(), закончится точкой с запятой, которой заканчивается инструкция.

Если вы вызываете функцию, которая ожидает ссылку на константу, скажем, void f(const int & n), с временным значением, например. f(2), компилятор создает временный int, инициализирует его 2 и передает ссылку на этот временный объект в функцию. Вы ожидаете, что этот временный объект завершит свою жизнь точкой с запятой в f(2);.

Теперь подумайте об этом:

int && ref = 2;
std::cout << ref;

Этот код вполне действителен. Обратите внимание, однако, что здесь компилятор также создает временный объект типа int и инициализирует его с помощью 2. Это временное, к которому привязывается ref. Однако если бы время жизни временного объекта было ограничено инструкцией, в которой оно создано, и заканчивалось точкой с запятой, отмечающей конец инструкции, следующая инструкция была бы катастрофой, поскольку cout использовала бы висячую ссылку. Таким образом, ссылки на временные объекты, подобные приведенным выше, были бы довольно непрактичными. Именно для этого и нужно продление срока службы временки. Я подозреваю, что компилятор, увидев что-то вроде int && ref = 2, может преобразовать его во что-то вроде этого

int tmp = 2;
int && ref = std::move(tmp);
std::cout << ref; // equivalent to std::cout << tmp;

Без расширения срока службы это могло бы выглядеть примерно так:

{
 int tmp = 2;
 int && ref = std::move(tmp);
}
std::cout << ref; // what is ref? 

Делать такой трюк в операторе return было бы бессмысленно. Не существует разумного и безопасного способа продлить время жизни любого объекта, локального для функции.

КСТАТИ. Большинство современных компиляторов выдают предупреждение и сокращают вашу функцию

int&& func()
{
    return 42;
}

to

int&& func()
{
    return nullptr;
}

с немедленным segfault при любой попытке разыменовать возвращаемое значение.

person zkoza    schedule 01.03.2021
comment
Большое спасибо за четкий и подробный ответ! - person Ruperrrt; 02.03.2021
comment
Там есть стрелка (или треугольник), указывающая вверх, на которую можно нажать ;-) - person zkoza; 03.03.2021
comment
Мои голоса не видны, потому что у меня меньше 15 репутации. :( - person Ruperrrt; 04.03.2021
comment
Возможно, вы примете ответ? Он должен быть где-то рядом с этими двумя треугольниками... :-) - person zkoza; 04.03.2021