一个临时对象的子对象在返回时是否保证被移动?

11
#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在返回时会被移动。

然而,我不确定:coll[0]是否也保证在返回时被移动?

更新:

#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输出相同:

已构造

已拷贝构造

已析构

已析构


2
不是的。复制可能会被省略,这种情况下就没有移动了。 - juanchopanza
1
你没有使用 f() 的返回值,所以需要移动什么? - Surt
一个左值可以被移动的条件与复制省略紧密耦合(请参见我的回答)。 - juanchopanza
2个回答

4

当返回一个本地对象时,既不会使用复制也不会使用移动,而是使用拷贝省略(Copy Elision),拷贝省略优于移动。这是因为控制本地对象的拷贝省略和移动的规则是相同的。如果要通过强制移动来实现,可以明确使用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会发出警告:

移动临时对象会阻止复制省略 [-Wpessimizing-move]

然而,你的代码情况不同。与返回一个对象(一个std::string)的std::ostringstream::str()不同,std::vector<>::operator[]返回一个引用,必须将其转换为对象(因为auto去除了引用)。在这种情况下,复制省略是不可能的(因为实际对象是具有非平凡析构函数的另一个对象的一部分),应该使用std::move()来避免复制。

这些考虑建议如果不确定,应该使用std::move(),但如果clang发出上述警告,则应该删除它。


不。在我的原始示例中,gcc 6.2和clang 3.8都没有执行复制省略。请参阅我的更新。 - xmllmx

3

“隐式移动”规则的最干净的表述在当前工作文件的[class.copy.elision]/3中:

在以下复制初始化上下文中,可能会使用移动操作而不是复制操作:

  • 如果返回语句([stmt.return])中的表达式是一个(可能带括号的)id-expression,它命名了具有自动存储期限的对象,在最内层封闭函数或lambda-expression参数声明子句的主体中声明,或者

  • [...]

为选择复制的构造函数执行重载决议时,首先按照rvalue指定对象进行。如果第一次重载决议失败或未执行,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能带有cv限定符),则再次执行重载决议,将对象视为lvalue。

b.acoll[0]都不是标识表达式,因此没有隐式移动。如果您需要移动操作,必须显式执行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接