Разрешают ли ссылки 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().

Компилятор вообще не видит этого, так что имейте в виду.

Другие вопросы по тегам