【问题标题】:Does returning a local object require move semantics?返回本地对象是否需要移动语义?
【发布时间】:2015-03-20 10:19:51
【问题描述】:

当按值返回本地对象时,C++ 编译器可能利用移动语义优化不必要的副本(复制省略)。
可能优化”意味着如果不满足适当的条件,则行为应退回到基于复制的默认按值返回语义。
因此,据我了解,按值返回可复制对象总是有效的。

但编译器(clang 和 gcc)似乎不同意我的解释,如下面的 MWE 所示。

class Foo {
public:
    Foo();
    Foo(const Foo&);
    Foo(Foo&&) = delete;
}

Foo f() { return Foo(); }  // error: call to explicitly deleted constructor of 'Foo'
Foo g() { Foo a; return a; }  // gcc complains, clang is fine
Foo x = g();  // error: call to explicitly deleted constructor of 'A'

Q1:按值返回是否要求对象是可移动的?
Q2:如果没有,gcc 和 clang 在我的 MWE 上是否表现不正常,还是我错过了其他东西?

【问题讨论】:

  • 由于NRVO/RVO可能直接调用构造函数省略移动和复制构造函数。虽然省略了调用,但移动和复制语法错误仍然可以阻止编译。
  • 你真的是要声明构造函数private吗?在Foo 中公开它们。
  • "...编译器可能会利用移动语义优化不必要的副本(复制省略)。..." -- 这是错误。 Copy Elison 和 Move Semantics 是两个正交的概念。当您复制 Elison 时,您既没有复制也没有移动。当您没有 Copy Elison 时,您可能会根据情况进行 Copy 或 Move 操作。
  • 并且即使省略也必须满足复制或移动的条件,所以如果复制或移动会使用私有或删除的构造函数,则程序格式错误。

标签: c++ c++11


【解决方案1】:

您只是满足重载解析的预期行为:Foo() 是一个右值,因此重载解析找到构造函数 Foo(Foo&&) 作为最佳匹配。由于删除了该重载,因此您的程序格式错误。此外,有一条特殊规则说Foo a; return a; 也将执行重载解析,就好像a 首先是一个右值。 (该规则基本上适用于 return 语句符合复制省略的条件。)

这一切都按预期工作。是你删除了超载,所以你明确要求禁止这种结构。

请注意,“真实”代码通常不会遇到这个障碍,因为一旦您声明了复制构造函数,您的类将根本没有任何移动构造函数。但是你特意说,“不,实际上我确实想要一个移动构造函数,如果有人试图使用它,我希望它是一个错误”。

【讨论】:

  • tl;dr 关键是删除的移动构造函数与没有移动构造函数不同。
  • @LightnessRacesinOrbit 请注意,如果删除的移动构造函数被隐式或显式默认,重载决议将忽略它,但如果它被显式删除则不会。
  • @Brian:确实,那是 [over.match.funcs]/8。
【解决方案2】:

关于这个:

Foo g() { Foo a; return a; }  // gcc complains, clang is fine

GCC 是对的,因为 [class.copy]/32 不应该编译(强调我的):

当满足省略复制/移动操作的条件时,但不满足异常声明,并且 要复制的对象由左值指定,或者当return 语句中的表达式为(可能 带括号的)id-expression,它命名具有在正文中声明的自动存储持续时间的对象或 最内层封闭函数或 lambda 表达式的参数声明子句,重载决议 首先执行为复制选择构造函数,就好像对象是由右值指定的一样。如果 第一个重载决议失败或未执行,或者如果所选的第一个参数的类型 构造函数不是对对象类型的右值引用(可能是 cv 限定的),重载决议是 再次执行,将对象视为左值。 [ 注意: 这个两阶段的重载决议必须是 无论是否会发生复制省略,都执行。如果省略,它确定要调用的构造函数 不执行,并且即使调用被省略,所选的构造函数也必须是可访问的。 —结束说明 ]

所以实现应该选择move构造函数进行省略,当它被删除时,程序是错误的。

【讨论】:

  • 是的,你说我想说。投票。
  • 我不认为 'Clang' 在这种情况下是错误的,因为 'rvalue' 的复制构造函数被声明为已删除 - 而不是此处隐式定义的 'lvalues' 的构造函数。事实上,“GCC”不编译这段代码是错误的。 (它不应该在 'Foo(const Foo&) = delete' 的情况下编译它)
  • 我读过它,它在您标记的文本之后指出 - ' 如果第一个重载解决方案失败或未执行,或者所选构造函数的第一个参数的类型不是对的右值引用对象的类型(可能是 cv 限定的),再次执行重载决议,将对象视为左值。这意味着'lvalue'的构造函数将被成功调用,这再次证明了我的和'clang'点。
  • @FISOCPP 右值的重载解析不会失败,因为Foo 有用户声明的移动构造函数。
猜你喜欢
  • 1970-01-01
  • 2012-06-20
  • 2022-01-01
  • 2011-10-20
  • 1970-01-01
  • 2017-06-17
  • 2014-05-29
  • 2015-07-21
相关资源
最近更新 更多