临时对象的子对象保证在 return 上移动吗?

Is the sub-object of a temporary object guaranteed to be moved on return?

#include <string>
#include <vector>

using namespace std;

auto f()
{
    vector<string> coll{ "hello" };

    //
    // Must I use move(coll[0]) ?
    //
    return coll[0]; 
}

int main()
{
    auto s = f();
    DoSomething(s);
}

我知道:如果我只是return coll;,那么coll肯定会被移动到return。

不过,我不确定:coll[0]是否也保证在return上移动?

更新:

#include <iostream>

struct A
{
    A() { std::cout << "constructed\n"; }
    A(const A&) { std::cout << "copy-constructed\n"; }
    A(A&&) { std::cout << "move-constructed\n"; }
    ~A() { std::cout << "destructed\n"; }
};

struct B
{
    A a;
};

A f()
{
    B b;
    return b.a;
}

int main()
{
    f();
}

gcc 6.2 和 clang 3.8 输出相同:

constructed

copy-constructed

destructed

destructed

返回本地对象时,既不会使用复制也不会使用移动,而是使用复制省略,后者优于移动。这是因为控制复制省略和本地对象移动的规则是相同的。当改为通过显式使用 std::move 强制移动时,如

template<typename T>
std::string make_string(T const& x)
{
  std::ostringstream str;
  str << x
  return std::move(str.str());    // not recommended
}

最新版本的 clang 发出警告

moving a temporary object prevents copy elision [-Wpessimizing-move]

但是,您代码中的情况有所不同。不像std::ostringstream::str(),其中returns一个对象(一个std::string),std::vector<>::operator[],returns一个引用,它必须转换为一个对象(因为auto 删除引用)。在这种情况下,复制省略是不可能的(因为实际对象是另一个具有非平凡析构函数的对象的一部分)并且应该使用 std::move() 来避免复制。

如果不确定,这些注意事项建议使用 std::move(),但如果 clang 问题高于警告,请将其删除。

"implicit move" 规则的最简洁表述在当前工作论文的 [class.copy.elision]/3 中:

In the following copy-initialization contexts, a move operation might be used instead of a copy operation:

  • If the expression in a return statement ([stmt.return]) is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or

  • [...]

overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue.

b.acoll[0] 都不是 id-expression。因此,没有隐式移动。如果你想移动,你必须明确地进行。