Подтвердить что ты не робот

Возвращаются локальные жители автоматически xvalues

Следуя из комментария, который я сделал по этому поводу:

передача std::vector в конструктор и перемещение семантики Требуется ли std::move в следующем коде, чтобы возвращаемое значение было значением x?

std::vector<string> buildVector()
{
  std::vector<string> local;

  // .... build a vector

  return std::move(local);
}

Я понимаю, что это требуется. Я часто видел, что это использовалось при возврате функции std::unique_ptr из функции, однако GManNickG сделал следующий комментарий:

Я понимаю, что в операторе return все локальные переменные автоматически являются значениями xvalues ​​(expating values) и будут перемещены, но я не уверен, что это относится только к возвращенному объекту. Поэтому OP должен идти вперед и помещать туда, пока я не уверен, что этого не должно быть.:)

Может ли кто-нибудь уточнить, нужен ли std::move?

Является ли зависимым от компилятора поведения?

4b9b3361

Ответ 1

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

Соответствующее примечание в 6.6.3 [ "Оператор возврата" ] (2):

Операция копирования или перемещения, связанная с оператором return, может быть отменена или рассматривается как rvalue с целью разрешения перегрузки при выборе конструктора (12.8).

Чтобы прояснить это, можно сказать, что возвращаемый объект может быть перемещен по ходу из локального объекта (хотя на практике RVO полностью пропустит этот шаг). Нормативной частью стандарта является 12.8 [ "Копирование и перемещение объектов класса" ] (31, 32), на копирование elision и rvalues ​​(спасибо @Mankarse!).


Вот глупый пример:

#include <utility>

struct Foo
{
    Foo()            = default;
    Foo(Foo const &) = delete;
    Foo(Foo &&)      = default;
};

Foo f(Foo & x)
{
    Foo y;

    // return x;         // error: use of deleted function ‘Foo::Foo(const Foo&)’
    return std::move(x); // OK
    return std::move(y); // OK
    return y;            // OK (!!)
}

Контрастируйте это, возвращая действительную ссылку rvalue:

Foo && g()
{
    Foo y;
    // return y;         // error: cannot bind ‘Foo’ lvalue to ‘Foo&&’
    return std::move(y); // OK type-wise (but undefined behaviour, thanks @GMNG)
}

Ответ 2

Хотя оба, return std::move(local) и return local, работают в смысле того, что они компилируют, их поведение отличается. И, вероятно, был предназначен только последний.

Если вы напишете функцию, которая возвращает std::vector<string>, вы должны вернуть std::vector<string> и точно это. std::move(local) имеет тип std::vector<string>&&, который не a std::vector<string>, поэтому он должен быть преобразован в него с помощью конструктора перемещения.

Стандарт гласит в 6.6.3.2:

Значение выражения неявно преобразуется в возвращаемый тип функции, в которой он появляется.

Это означает, что return std::move(local) равнозначен

std::vector<std::string> converted(std::move(local); // move constructor
return converted; // not yet a copy constructor call (which will be elided anyway)

тогда как return local только

return local; // not yet a copy constructor call (which will be elided anyway)

Это избавит вас от одной операции.


Чтобы дать вам краткий пример того, что это означает:

struct test {
  test() { std::cout << "  construct\n"; }
  test(const test&) { std::cout << "  copy\n"; }
  test(test&&) { std::cout << "  move\n"; }
};

test f1() { test t; return t; }
test f2() { test t; return std::move(t); }

int main()
{
  std::cout << "f1():\n"; test t1 = f1();
  std::cout << "f2():\n"; test t2 = f2();
}

Это приведет к выводу

f1():
  construct
f2():
  construct
  move

Ответ 3

Я думаю, что ответ отрицательный. Хотя официально только примечание, §5/6 суммирует, какие выражения/не являются значениями x:

Выражение - это значение x, если оно:

  • результат вызова функции, неявно или явно, чей тип возврата является ссылкой rvalue на тип объекта,
  • ссылка на ссылку rvalue для типа объекта,
  • выражение доступа к члену класса, обозначающее нестатический элемент данных не ссылочного типа, в котором выражение объекта является значением x, или
  • a. * выражение pointer-to-member, в котором первым операндом является xvalue, а второй операнд является указателем на элемент данных.

В общем, эффект этого правила заключается в том, что именованные ссылки rvalue рассматриваются как lvalues, а неназванные ссылки rvalue на объекты рассматриваются как xvalues; Ссылки rvalue на функции обрабатываются как lvalues, именованные или нет.

Здесь, похоже, применяется первая точка. Поскольку рассматриваемая функция возвращает значение, а не ссылку rvalue, результат не будет значением x.