147

在这种情况下

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?
}

那里需要搬家,我想?

4

6 回答 6

151

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

当满足或将满足复制操作的省略标准时,除了源对象是函数参数的事实,并且要复制的对象由左值指定时,选择复制的构造函数的重载决策是首先执行好像对象是由一个右值指定的。

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

但是,添加move确实有潜在的影响:它可以防止移动被忽略,因为return std::move(foo);符合 NRVO 的条件。

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

因此,要回答标题中的问题,std::move当您希望它被移动并且它不会被移动时,请使用返回值。那是:

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

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

  • 你希望它被移动,并且
  • 它是一个左值,并且
  • 你不必担心它。

通过遵循简化的规则,您会牺牲一些移动省略。对于std::vector那些移动起来很便宜的类型,你可能永远不会注意到(如果你注意到了,你可以优化)。对于像std::array这样移动起来很昂贵的类型,或者对于你不知道移动是否便宜的模板,你更有可能担心它。

于 2013-02-13T15:07:24.790 回答
41

在这两种情况下,移动都是不必要的。在第二种情况下,std::move这是多余的,因为您要按值返回局部变量,并且编译器会理解,由于您不再使用该局部变量,因此可以将其移出而不是复制。

于 2013-02-13T14:58:38.517 回答
30

在返回值上,如果返回表达式直接引用本地左值的名称(即此时为 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可能会影响其他优化。

于 2013-02-13T15:39:29.730 回答
23

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

下面是一个人为的例子,说明何时应该使用它:

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(并且这个隐式移动可以被忽略,而显式移动不能)。如果你有一个函数或操作接受局部变量,并返回对所述局部变量的引用,你必须std::move让 move 发生(例如,三元?:运算符)。

于 2013-02-13T15:36:36.520 回答
2

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

  • 如果已知它foo已经到了生命周期的尽头,并且
  • 除了 C++ 规范允许的语义效果之外,隐式使用std::move不会对 C++ 代码的语义产生任何影响。

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


从概念上讲,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 年的程序员需要明确指定此类优化。

于 2017-02-27T22:49:25.223 回答
-8

std::move从函数返回时完全没有必要,并且真正进入了你的领域——程序员——试图照看你应该留给编译器的东西。

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

另一方面,编译器大多不会犯这类错误。

另外:需要注意的是,从函数返回局部变量不一定会创建右值或使用移动语义。

看这里。

于 2013-02-13T15:01:52.120 回答