【问题标题】:Why explicit std::move is needed when returning compatible type?为什么返回兼容类型时需要显式 std::move?
【发布时间】:2015-06-25 08:44:02
【问题描述】:

我正在观看 STL 的“Don’t Help the Compiler”演讲,他在幻灯片 26 上有一个类似的例子:

struct A
{
  A() = default;
  A(const A&) { std::cout << "copied" << std::endl; }
  A(A&&) { std::cout << "moved" << std::endl; }
};

std::pair<A, A> get_pair()
{
  std::pair<A, A> p;
  return p;
}

std::tuple<A, A> get_tuple()
{
  std::pair<A, A> p;
  return p;
}

std::tuple<A, A> get_tuple_moved()
{
  std::pair<A, A> p;
  return std::move(p);
}

有了这个,下面的调用:

get_pair();
get_tuple();
get_tuple_moved();

产生这个输出:

moved
moved
copied
copied
moved
moved

See MCVE in action.

get_pair 的结果是移动构造的,正如预期的那样。一个动作也可能被 NRVO 完全忽略,但它不在当前问题的主题范围内。

get_tuple_moved 的结果也是移动构造的,这是明确指定的。但是,get_tuple 的结果是复制构造的,这对我来说完全不明显。

我认为传递给return 语句的任何表达式都可能被认为具有隐含的move,因为编译器知道它无论如何都会超出范围。好像我错了。有人可以澄清一下,这里发生了什么吗?

另请参阅相关但不同的问题:When should std::move be used on a function return value?

【问题讨论】:

  • 您是否禁用了复制省略?另外,最好发布一个真正的 MCVE。
  • @juanchopanza 其实是一个真正的MCVE,只是把调用方法放在main()里。无论 RVO 如何,get_tupleget_tuple_moved 的行为都是相同的,而 get_pair 会受到影响。
  • @Mikhail:所以它实际上 不是 一个真正的 MCVE(因为它需要比复制和粘贴更多的工作,尽管只是其中的一小部分):-)
  • @DietmarKühl,好的,你说得对,我会解决的:)

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


【解决方案1】:

get_tuple() 中的 return 语句应该使用 move-constructor 进行复制初始化,但由于返回表达式的类型和返回类型不匹配,因此选择了 copy-constructor。在 C++14 中进行了更改,现在有一个重载解析的初始阶段,当它只是一个在主体中声明的自动变量时,它会将 return 语句视为右值。

相关措辞可见[class.copy]/p32:

当满足省略复制/移动操作的条件时,[..],或者当 return 语句中的表达式是(可能带括号的)id 表达式时,它命名具有自动存储持续时间的对象在正文中声明 [..],首先执行为复制选择构造函数的重载决策,就好像对象是由右值指定的一样。

所以在 C++14 中,所有输出都应该来自 A 的移动构造函数。

clang 和 gcc 的 Trunk 版本已经实现了此更改。要在 C++11 模式下获得相同的行为,您需要在 return 语句中使用显式 std::move()。

【讨论】:

  • 所以基本上,这是一种非标准行为,至少发生在gcc-4.9.2Visual Studio 2015 CTP 6?
  • @Mikhail 这是 C++11 中的标准行为,但它们不符合最新标准 (C++14)。
  • Clang 实际上还没有实现这个改变,但是 GCC 已经实现了。
猜你喜欢
  • 2012-07-28
  • 1970-01-01
  • 1970-01-01
  • 2015-05-10
  • 2021-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多