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
什么?*this
.这与此有关吗?在C++ 14中会有上述任何变化吗?rod*_*igo 11
类型this
取决于成员函数的cv限定符:Avenger*
或const Avenger*
但不是它的参赛资格赛.该REF-限定符仅用于确定待调用的函数.
因此,类型*this
是Avenger&
或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中我没有听到任何有关此事的变化,但我可能会弄错...
std::move
也许更好的称呼rvalue_cast
.
但它并没有被称为.尽管它的名字,它只不过是一个左右的演员:std::move
不动.
所有命名值都是左值,因为所有指针解引用都是如此,因此使用std::move
或std::forward
(也称为条件右值转换)将声明(或其他原因)处的右值引用的命名值转换为特定点的右值是犹太的.
但请注意,您很少想要返回右值引用.如果你的类型移动便宜,你通常想要返回一个文字.这样做std::move
在方法体中使用相同的方法,但现在它实际上触发了移动到返回值.现在,如果您在引用(例如auto&& foo = expression;
)中捕获返回值,则引用生存期扩展正常工作.关于返回右值引用的唯一好时机是rvalue强制转换:哪种方式使得move
rvalue转换的事实在某种程度上是学术性的.
返回限定符超载的成员是不行的。这里的问题不在于(其他答案正确地表明它是可以的),而是在于返回类型。这个问题非常微妙,几乎是由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\nvoid 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
(无右值引用重载):
void doInjustice(Avenger const &) {};\n
Run Code Online (Sandbox Code Playgroud)\n\n如果引用函数内的参数,接下来的两个调用将导致 UB:
\n\ndoInjustice(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\nclass 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示例Stephan T. Lavavej
:Don\xe2\x80\x99t 帮助编译器(42m\xe2\x80\x9345m)
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\nSO 上的帖子通过引用解释了临时对象的寿命延长:
\n\n\n