Ссылки на константу и продление жизни временных объектов в С++
Изучаю perfect forwarding + move semantic и решил подробнее сформулировать для себя некоторые моменты работы ссылок в C++. Они так или иначе касаются константных ссылок на временные объекты. Нужен контроль, что я правильно усвоил эти моменты.
int main( )
{
const int& ref = 1;
cout << ref << endl; // ок
}
Таким образом, время жизни rvalue продлевается до конца времени жизни ref, т.е. до конца функции main( ). Соответственно, если передать ref как параметр в к-л функцию, все будет работать корректно.
void foo(int& arg);
foo(ref); // ок
Аргумент arg просто копирует значение ссылки ref, которая продолжает жить.
Если же, наоборот, использовать возврат из функции по ссылке на константу на временный объект внутри самой функции, получится висячая ссылка.
const int& foo( )
{
const int& i = 1;
return i;
}
const int& ref = foo( ); // ошибка
Здесь снова все логично, т.к. значение ссылки i при возврате из функции копируется в ref, а сама i уничтожается. Следовательно, 'уничтожается' и '1'. Вообще-то, возможна ситуация, при которой она лежала в стеке и еще может существовать по адресу в памяти, на который ссылается ref. Но до тех пор, пока не будет затерта данными нового фрейма стека. Возвращаясь к предыдущему примеру:
const int& ref = foo( );
cout << ref << endl; // ошибка (хотя может повезти с правильным значением)
const int& ref = foo( );
boo( ); // затирается фрейм, который создавался foo( )
cout << ref << endl; // ошибка (совсем ошибка)
В то же время, если использовать возврат по значению, то вполне можно проинициализировать им ссылку на константу:
int foo( ) { return 1; }
const int& ref = foo( ); // ок
Кроме того, базовое правило для временного объекта гласит, что уничтожение временного объекта это последний шаг вычисления full-expression, внутри которого этот временный объект был создан. Следовательно, при возврате по значению можно не только проинициализировать им ссылку на константу, но и использовать тут же, в пределах full-expression:
void print(const int& arg)
{
cout << arg << endl;
}
int foo( )
{
return 1;
}
print(foo( )); // ок
Правильно ли, что место всегда выделяется на стеке?
Есть ли еще критически важные нюансы?
Прошу прокомментировать / поправить / дополнить.
Заранее благодарен всем, кто внесет свою лепту!
*** UPDATE ***
Благодарность KoVadim, который помог разобраться с отличием продления жизни временного объекта и возвратом значения из функции по ссылке на константу:
Последний пример = продление жизни временного объекта, т.к. именно такое временное значение возвращается из foo( ) и тут же присваивается аргументу print( ) - стандарт, §6.7.7, 5-6 (6.9)
Но если foo( ) возвращает const int&, то даже сразу вызвать print(foo( )); будет ошибкой, поскольку 'манипуляции орудуют с трупиком'
*** UPDATE 2 ***
Благодарность user7860670 за указание на то, что размещение переменных с автоматическим временем хранения зависит от компилятора.
На системах с аппаратным стеком такие действительно часто размещаются на стеке. Однако они могут размещаться и в регистрах (особенно если не брать адрес), а константы могут запросто попадать и в блок статических данных или даже встраиваться в машинный код.