何时应该在函数返回值上使用std :: move?

时间:2013-02-13 14:55:56

标签: c++ c++11 move-semantics

在这种情况下

struct Foo {};
Foo meh() {
  return std::move(Foo());
}

我很确定移动是不必要的,因为新创建的Foo将是一个xvalue。

但是在这种情况下呢?

struct Foo {};
Foo meh() {
  Foo foo;
  //do something, but knowing that foo can safely be disposed of
  //but does the compiler necessarily know it?
  //we may have references/pointers to foo. how could the compiler know?
  return std::move(foo); //so here the move is needed, right?
}

我认为需要采取行动吗?

6 个答案:

答案 0 :(得分:106)

return std::move(foo);的情况下,由于12.8 / 32,move是多余的:

  

当符合或将要执行复制操作的标准时   因为源对象是一个函数参数这个事实,   并且要复制的对象由左值,超载指定   选择复制构造函数的分辨率首先执行为   如果对象是由右值指定的。

return foo;是NRVO的案例,因此允许复制省略。 foo是一个左值。因此,从foomeh的返回值选择的“复制”的构造函数必须是移动构造函数(如果存在)。

添加move确实会产生潜在影响:它会阻止移动被删除,因为return std::move(foo);

据我所知,12.8 / 32列出了条件,在此条件下,左值的副本可以被移动替换。一般情况下,编译器不允许在复制后检测左值未使用(比如使用DFA),并自行进行更改。我在这里假设两者之间存在可观察到的差异 - 如果可观察行为相同则应用“假设”规则。

因此,要回答标题中的问题,请在返回值时使用std::move,并且无论如何都不会移动它。那就是:

  • 你希望它被移动,
  • 这是一个左值,
  • 它不符合复制条款,
  • 它不是按值函数参数的名称。

考虑到这是非常繁琐而且移动通常便宜,您可能会说在非模板代码中您可以简化这一点。在以下情况下使用std::move

  • 你希望它被移动,
  • 这是一个左值,
  • 你不用担心它。

按照简化的规则,你牺牲了一些移动省略。对于像std::vector那样移动便宜的类型,你可能永远都不会注意到(如果你注意到你可以优化)。对于移动费用较高的std::array类型,或者您不知道移动是否便宜的模板,您更有可能会担心它。

答案 1 :(得分:31)

这两种情况都不需要采取行动。在第二种情况下,std::move是多余的,因为您按值返回局部变量,编译器将理解,因为您不再使用该局部变量,所以它可以从而不是被复制。

答案 2 :(得分:15)

在返回值上,如果返回表达式直接引用本地左值的名称(即此时为xvalue),则不需要std::move。另一方面,如果返回表达式标识符,则不会自动移动它,例如,在这种情况下,您需要显式的std::move

T foo(bool which) {
   T a = ..., b = ...;
   return std::move(which? a : b);
   // alternatively: return which? std::move(a), std::move(b);
}

直接返回命名局部变量或临时表达式时,应避免使用显式std::move。在这些情况下,编译器必须(以及将来会自动移动),添加std::move可能会影响其他优化。

答案 3 :(得分:13)

关于什么时候不应该移动,有很多答案,但问题是“什么时候应该移动?”

以下是应该使用它的一个人为设想的例子:

std::vector<int> append(std::vector<int>&& v, int x) {
  v.push_back(x);
  return std::move(v);
}

即,当你有一个带右值引用的函数时,修改它,然后返回它的副本。现在,在实践中,这种设计几乎总是更好:

std::vector<int> append(std::vector<int> v, int x) {
  v.push_back(x);
  return v;
}

还允许您使用非右值参数。

基本上,如果您想要通过移动返回的函数中有右值引用,则必须调用std::move。如果你有一个局部变量(无论是否为参数),则隐式返回move s(这种隐式移动可以省略,而显式移动则不能)。如果您有一个接受局部变量的函数或操作,并返回对所述局部变量的引用,则必须std::move才能移动(例如,三元?:运算符)。 / p>

答案 4 :(得分:0)

C ++编译器可以免费使用std::move(foo)

  • 如果知道foo在其生命周期结束时,
  • 除了C ++规范允许的语义效果之外,std::move的隐式使用不会对C ++代码的语义产生任何影响。

它取决于C ++编译器的优化功能,是否能够计算从f(foo); foo.~Foo();f(std::move(foo)); foo.~Foo();的哪些转换在性能或内存消耗方面是有利可图的,同时遵守C ++规范规则。

从概念上讲,2017年的C ++编译器,如GCC 6.3.0,能够优化此代码:

Foo meh() {
    Foo foo(args);
    foo.method(xyz);
    bar();
    return foo;
}

进入此代码:

void meh(Foo *retval) {
   new (retval) Foo(arg);
   retval->method(xyz);
   bar();
}

避免调用Foo的复制构造函数和析构函数。

2017年的C ++编译器,例如GCC 6.3.0,无法优化这些代码:

Foo meh_value() {
    Foo foo(args);
    Foo retval(foo);
    return retval;
}

Foo meh_pointer() {
    Foo *foo = get_foo();
    Foo retval(*foo);
    delete foo;
    return retval;
}

进入这些代码:

Foo meh_value() {
    Foo foo(args);
    Foo retval(std::move(foo));
    return retval;
}

Foo meh_pointer() {
    Foo *foo = get_foo();
    Foo retval(std::move(*foo));
    delete foo;
    return retval;
}

这意味着2017年的程序员需要明确指定这样的优化。

答案 5 :(得分:-6)

从函数返回时,

std::move是完全没必要的,并且真正进入你的领域 - 程序员 - 试图照看你应该留给编译器的东西。

当你std::move某个函数不是该函数的局部变量时会发生什么?你可以说你永远不会写那样的代码,但如果你编写的代码很好,然后重构它并且心不在焉地不改变std::move会发生什么。你可以很好地跟踪这个错误。

另一方面,编译器几乎无法犯这些错误。

另外:重要的是要注意从函数返回局部变量必然会创建一个右值或使用移动语义。

See here.