Разрешают ли ссылки rvalue висячие ссылки?
Рассмотрим ниже.
#include <string>
using std::string;
string middle_name () {
return "Jaan";
}
int main ()
{
string&& danger = middle_name(); // ?!
return 0;
}
Это ничего не вычисляет, но компилирует без ошибок и демонстрирует то, что я нахожу запутанным: danger
это свисающая ссылка, не так ли?
4 ответа
Разрешают ли ссылки rvalue висячие ссылки?
Если вы имели в виду "Можно ли создать висячие ссылки на значения", тогда ответ - да. Ваш пример, однако,
string middle_name () {
return "Jaan";
}
int main()
{
string&& nodanger = middle_name(); // OK.
// The life-time of the temporary is extended
// to the life-time of the reference.
return 0;
}
отлично в порядке. Здесь применяется то же правило, что делает этот пример (статья Херба Саттера) также безопасным. Если вы инициализируете ссылку с чистым значением, время жизни временного объекта увеличивается до времени жизни ссылки. Тем не менее, вы все еще можете создавать висячие ссылки. Например, это больше не безопасно:
int main()
{
string&& danger = std::move(middle_name()); // dangling reference !
return 0;
}
Так как std::move
возвращает string&&
(которое не является чистым значением) правило, которое продлевает время жизни временного, не применяется. Вот, std::move
возвращает так называемое xvalue. Xvalue - это просто неназванная ссылка на rvalue. Как таковая, она может ссылаться на что угодно, и в принципе невозможно угадать, на что ссылается возвращаемая ссылка, не глядя на реализацию функции.
Ссылки rvalue связываются с rvalue. Rvalue - это либо prvalue, либо xvalue [ объяснение]. Привязка к первому никогда не создает висячей ссылки, привязка ко второму могуществу. Вот почему вообще плохая идея выбирать T&&
в качестве типа возврата функции. std::move
является исключением из этого правила.
T& lvalue();
T prvalue();
T&& xvalue();
T&& does_not_compile = lvalue();
T&& well_behaved = prvalue();
T&& problematic = xvalue();
danger
это свисающая ссылка, не так ли?
Не более, чем если бы вы использовали const &
: danger
вступает во владение rvalue.
Конечно, ссылка rvalue по-прежнему является ссылкой, поэтому она также может быть висящей. Вам просто нужно поставить компилятор в ситуацию, когда он должен перетаскивать ссылку, и в то же время вы просто выходите из области действия, на которую указывает ссылка, например:
#include <cstdio>
#include <tuple>
std::tuple<int&&> mytuple{ 2 };
auto pollute_stack()
{
printf("Dumdudelei!\n");
}
int main()
{
{
int a = 5;
mytuple = std::forward_as_tuple<int&&>(std::move(a));
}
pollute_stack();
int b = std::get<int&&>(mytuple);
printf("Hello b = %d!\n", b);
}
Выход:
Dumdudelei!
Hello b = 0!
Как видите, b теперь имеет неправильное значение. Почему? Мы поместили ссылку rvalue на автоматическую переменную в глобальный кортеж. Затем мы вышли из области действия и получили его значение черезstd::get<int&&
> который будет оцениваться как rvalue-ссылка. Таким образом, новый объект b на самом деле является перемещением, созданным из , но компилятор не находит его, поскольку его область действия уже закончилась. Поэтомуstd::get<int&&>
оценивается как 0 (хотя, вероятно, это UB и может оцениваться как угодно).
Обратите внимание, что если мы не коснемся стека, ссылка rvalue на самом деле все равно найдет исходное значение объекта.a
даже после того, как его область действия закончилась, и он получит правильное значение (просто попробуйте, раскомментируйте и посмотрите, что произойдет). pollute_stack()
просто перемещает указатель стека вперед и назад при записи значений в стек, выполняя некоторые действия, связанные с вводом-выводом, черезprintf()
.
Компилятор вообще не видит этого, так что имейте в виду.