返回的本地人自动xvalues

mar*_*ark 18 c++ move-semantics c++11

继我对此做出的评论之后:

将std :: vector传递给构造函数并移动语义 是否std::move在下面的代码中是必要的,以确保返回的值是xvalue?

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

  // .... build a vector

  return std::move(local);
}
Run Code Online (Sandbox Code Playgroud)

我的理解是这是必需的.我经常看到std::unique_ptr从函数返回时使用的这个,但是GManNickG发表了以下评论:

我的理解是,在一个return语句中,所有局部变量都是自动xvalues(到期值)并将被移动,但我不确定它是否仅适用于返回的对象本身.所以OP应该继续把它放在那里,直到我更加自信它不应该是.:)

任何人都可以澄清是否std::move有必要吗?

行为编译器是否依赖?

Ker*_* SB 15

您可以保证local在这种情况下将作为右值返回.通常编译器会执行返回值优化,但在此之前甚至会成为问题,并且您可能根本看不到任何实际移动,因为local对象将直接在调用站点构建.

6.6.3中的相关说明 ["退货声明"](2):

在选择构造函数(12.8)时,为了重载解析的目的,可以省略或将与返回语句相关联的复制或移动操作视为右值.

为了澄清,这就是说返回的对象可以从本地对象移动构造(即使实际上RVO将完全跳过此步骤).标准的规范部分是12.8 ["复制和移动类对象"](31,32),复制省略和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 (!!)
}
Run Code Online (Sandbox Code Playgroud)

将此与返回实际右值参考进行对比:

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)
}
Run Code Online (Sandbox Code Playgroud)


ipc*_*ipc 7

尽管两者都是,return std::move(local)并且return local在他们编译的意义上工作,但他们的行为是不同的.并且可能只有后一个是有意的.

如果你编写一个返回a的函数std::vector<string>,你必须返回一个 std::vector<string>完全正确的函数.std::move(local)具有类型std::vector<string>&&不是一个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)
Run Code Online (Sandbox Code Playgroud)

return local只是

return local; // not yet a copy constructor call (which will be elided anyway)
Run Code Online (Sandbox Code Playgroud)

这使您免于一次操作.


给你一个简短的例子:

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();
}
Run Code Online (Sandbox Code Playgroud)

这将输出

f1():
  construct
f2():
  construct
  move
Run Code Online (Sandbox Code Playgroud)


Jer*_*fin 5

我认为答案是否定的。虽然官方只是一个注释,§5/6 总结了哪些表达式是/不是 xvalues:

表达式是 xvalue,如果它是:

  • 调用函数的结果,无论是隐式还是显式,其返回类型是对对象类型的右值引用,
  • 转换为对对象类型的右值引用,
  • 指定非引用类型的非静态数据成员的类成员访问表达式,其中对象表达式是 xvalue,或
  • .* 成员指针表达式,其中第一个操作数是 xvalue,第二个操作数是指向数据成员的指针。

一般来说,这条规则的作用是,命名的右值引用被视为左值,而对对象的未命名右值引用被视为xvalues;无论是否命名,对函数的右值引用都被视为左值。

第一个要点似乎适用于此。由于所讨论的函数返回的是值而不是右值引用,因此结果不会是 xvalue。