正确的方法返回一个右值引用

bol*_*lov 15 c++ rvalue c++11 c++14

下面的代码导致未定义的行为.请务必阅读所有答案的完整性.


通过operator<<我链接对象时,我想保留对象的左值/右值:

class Avenger {
  public:
    Avenger& operator<<(int) & {
      return *this;
    }
    Avenger&& operator<<(int) && {
      return *this; // compiler error cannot bind lvalue to rvalue
      return std::move(*this);
    }
};
void doJustice(const Avenger &) {};
void doJustice(Avenger &&) {};

int main() {
  Avenger a;
  doJustice(a << 24); // parameter should be Avenger&
  doJustice(Avenger{} << 24); // parameter should be Avenger&&

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我不能简单地返回*this这意味着类型*this的的rvalue对象仍然是一个lvalue reference.我原以为是rvalue reference.

  • 是否正确/建议返回std::move(*this)&&限定符重载的成员,还是应该使用其他方法?我知道这std::move只是演员,所以我觉得没关系,我只是想仔细检查一下.
  • *this一个rvalue是一个lvalue reference而不是一个是什么原因/解释是rvalue reference什么?
  • 我记得在C++ 14中看过一些关于移动语义的东西*this.这与此有关吗?在C++ 14中会有上述任何变化吗?

rod*_*igo 11

类型this取决于成员函数的cv限定符:Avenger*const Avenger*

但不是它的参赛资格赛.该REF-限定符仅用于确定待调用的函数.

因此,类型*thisAvenger&const Avenger&,不管你使用&&与否.区别在于&&将使用重载,然后被调用的对象是r值,而&不会.

请注意,rvalue-ness是表达式的属性,而不是对象.例如:

void foo(Avenger &x)
{
    foo(x); //recursive call
}
void foo(Avenger &&x)
{
    foo(x); //calls foo(Avenger &)!
}
Run Code Online (Sandbox Code Playgroud)

也就是说,虽然在第二个中foo(),x被定义为r值引用,但表达式的任何使用x仍然是l值.同样如此*this.

所以,如果你想移出对象,那return std::move(*this)就是正确的方法.

事情有所不同this被定义为参考值而不是指针吗?我不确定,但我认为考虑*this作为一个r值可能导致一些疯狂的情况......

在C++ 14中我没有听到任何有关此事的变化,但我可能会弄错...

  • 即使将"this"定义为参考,也不会产生任何影响.`int && f(int && x){return x; }`无效,因为在`f`里面,`x`是左值. (2认同)

Yak*_*ont 7

std::move也许更好的称呼rvalue_cast.

但它并没有被称为.尽管它的名字,它只不过是一个左右的演员:std::move不动.

所有命名值都是左值,因为所有指针解引用都是如此,因此使用std::movestd::forward(也称为条件右值转换)将声明(或其他原因)处的右值引用的命名值转换为特定点的右值是犹太的.

但请注意,您很少想要返回右值引用.如果你的类型移动便宜,你通常想要返回一个文字.这样做std::move在方法体中使用相同的方法,但现在它实际上触发了移动到返回值.现在,如果您在引用(例如auto&& foo = expression;)中捕获返回值,则引用生存期扩展正常工作.关于返回右值引用的唯一好时机是rvalue强制转换:哪种方式使得movervalue转换的事实在某种程度上是学术性的.


bol*_*lov 2

返回限定符超载的成员是不行的。这里的问题不在于(其他答案正确地表明它是可以的),而是在于返回类型。这个问题非常微妙,几乎是由c++11标准化委员会完成的。Stephan T. Lavavej在他的演讲Don \xe2\x80\x99t Help the Compiler during . 他的示例可以在链接视频的第 42 分钟左右找到。他的例子略有不同,并且\xe2\x80\x99t不涉及std::move(*this)&&std::move(*this)Going Native 2013*this并使用参数引用类型而不是方法引用限定符的重载,但原理仍然相同。

\n\n

那么代码有什么问题呢?

\n\n

简短介绍:绑定到临时对象的引用将临时对象的生命周期延长到引用的生命周期。这就是使这样的代码可以正常运行的原因:

\n\n
void foo(std::string const & s) {\n  //\n}\n\nfoo("Temporary std::string object constructed from this char * C-string");\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里重要的一点是这个属性是不可传递的,这意味着为了延长临时对象的生命周期,它必须直接绑定绑定到临时对象,而不是绑定到它的引用。

\n\n

回到我的例子:

\n\n

为了完整性,let\xe2\x80\x99s 添加一个仅接受 const 左值引用的函数Avenger(无右值引用重载):

\n\n
void doInjustice(Avenger const &) {};\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果引用函数内的参数,接下来的两个调用将导致 UB:

\n\n
doInjustice(Avenger{} << 24); // calls `void doInustice(Avenger const &) {};`\ndoJustice(Avenger{} << 24); // calls `void doJustice(Avenger &&) {};` \n
Run Code Online (Sandbox Code Playgroud)\n\n

由于上述原因,一旦调用函数,参数求值时构造的临时对象就会被销毁,并且参数是悬空引用。在函数内部引用它们将产生 UB。

\n\n
\n\n

正确是按值返回:

\n\n
class Avenger {\n  public:\n    Avenger& operator<<(int) & {\n      return *this;\n    }\n    Avenger operator<<(int) && {\n      return std::move(*this);\n    }\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

移动语义仍然无法避免副本,并且返回是临时的,这意味着它将调用正确的重载,但我们避免了这种微妙但令人讨厌的无声错误。

\n\n
\n\n

示例Stephan T. LavavejDon\xe2\x80\x99t 帮助编译器(42m\xe2\x80\x9345m)

\n\n
string&& join(string&& rv, const char * ptr) {\n  return move(rv.append(", ").append(ptr));\n}\nstring meow() { return "meow"; }\n\nconst string& r = join(meow(), "purr");\n// r refers to a destroyed temporary!\n\n//Fix:\nstring join(string&& rv, const char * ptr) {\n  return move(rv.append(", ").append(ptr));\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

SO 上的帖子通过引用解释了临时对象的寿命延长:

\n\n\n

  • [完整表达式](http://stackoverflow.com/questions/4214153/lifetime-of-temporaries) 不是子表达式。在这种情况下,完整的表达式“结束”于`;`(通常是这种情况)。因此 `doJustice(Avenger{}.operator&lt;&lt;(24))` 有一个子表达式 `Avenger{}.operator&lt;&lt;(24)`。临时的 `Avenger{}` 被传递给 `operator&lt;&lt;`,并且持续到封闭的**完整表达式** `doJustice(Avenger{}.operator&lt;&lt;(24));` 的末尾。OP 中没有任何内容是 UB:它与 UB 相邻,并且在强制转换之外返回 `&amp;&amp;` 是一种不好的做法,但这篇文章的详细信息是错误的。 (3认同)