了解左值/右值表达式与对象类型

时间:2019-01-23 02:00:17

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

我已经阅读了一些先前的最佳答案以及Stroustrup的“ C ++编程语言”和“有效的现代C ++”,但是我很难真正理解表达式的左值/右值方面与其区别之间的区别。类型。在“有效的现代C ++”的简介中说:

  

确定表达式是否为左值的一种有用的启发式方法是询问您是否可以使用其地址。如果可以,通常是这样。如果不能,通常是右值。这种启发式的一个不错的功能是,它可以帮助您记住表达式的类型与表达式是左值还是右值无关。在处理右值引用类型的参数时,记住这一点尤其重要,因为参数本身是一个左值。

我不了解某些内容,因为我不明白为什么如果您有右值引用类型参数,则需要通过std::move()实际上将其强制转换为右值以使其有资格被移动。即使参数(所有参数)是左值,编译器也知道其类型是右值引用,那么为什么需要告诉编译器可以移动它呢?这似乎是多余的,但我想我不理解表达式的类型与左值/右值性质(不确定正确的术语)之间的区别。

编辑:

要跟进下面的一些答案/评论,尚不清楚的是为什么在下面的doSomething()中我需要将参数包装在std::move()中以使其绑定到右值引用并解析为doSomethingElse()的第二版。我知道,如果要隐式发生这种情况,那将是不好的,因为该参数将被从中移出,并且在此之后可能会无意中使用它。似乎参数的右值引用类型本质在函数中毫无意义,因为给定右值作为参数传入时,其唯一目的是绑定解析为函数的正确版本。

Widget getWidget();
void doSomethingElse(Widget& rhs);  // #1
void doSomethingElse(Widget&& rhs); // #2

void doSomething(Widget&& rhs) {
  // will call #1
  doSomethingElse(rhs);
  // will call #2
  doSomethingElse(std::move(rhs));      
}

int main() {
  doSomething(getWidget());
}

3 个答案:

答案 0 :(得分:2)

  

我不明白为什么如果您有右值引用类型参数,则需要通过std::move()将其实际转换为右值以使其有资格被移动。

正如引号所说,typesvalue categories是不同的东西。参数始终是左值,即使其类型是右值引用;我们必须使用std::move将其绑定到右值引用。假设我们允许编译器隐式地执行此操作,如以下代码段所示,

void foo(std::string&& s);
void bar(std::string&& s) {

    foo(s);  

    // continue to use s...
    // oops, s might have been moved

    foo(std::string{}); // this is fine;
                        // the temporary will be destroyed after the full expression and won't be used later

}

因此,我们必须显式使用std::move,以告知编译器我们知道我们要执行的操作。

void bar(std::string&& s) {

    foo(std::move(s));  

    // we know that s might have been moved
}

答案 1 :(得分:0)

存在RValue引用来解决转发问题。 C ++中现有的类型推导规则使得不可能具有一致且合理的移动语义。因此,对类型系统进行了扩展,并引入了新的规则,以使其更加复杂但一致。

仅当您从已解决的问题的角度来看问题时,这才有意义。这是一个很好的link,专门用于解释RValue引用。

答案 2 :(得分:0)

我认为您实际上已经掌握了类型值类别之间的区别,所以我将重点介绍两个特定的声明/查询:

  

您实际上需要通过std :: move()将其强制转换为右值,以使其有资格被移动

排序,但不是真的。将重命名或引用您的对象的表达式强制转换为右值,可以使我们在重载解析期间触发占用Type&&的函数重载。习惯上,我们要转让所有权时这样做,但这与使它“有资格被转移”并不完全相同,因为转移可能不是您最终要做的。从某种意义上说,这是一种挑剔,尽管我认为理解很重要。因为:

  

即使参数(所有参数)都是左值,编译器也知道其类型是右值引用,所以为什么需要告诉编译器它可以移动?

除非您编写std::move(theThing),否则它是一个临时值(已经是一个右值),那么它不是一个右值,因此它不能绑定到右值引用。这就是所有设计和定义的方式。它是故意这样做的,以便左值表达式,命名事物的表达式,尚未写std::move()的事物不会绑定到右值引用。因此,您的程序将不会编译,或者,如果有的话,重载解析将改为选择可能包含const Type&的函数版本-并且我们知道不会涉及所有权转移。

tl; dr:编译器不知道其类型是右值引用,因为它不是一个。就像您无法int& ref = 42一样,您也无法int x = 42; int&& ref = x;。否则,它将尝试移动一切!关键是要使某些类型的引用仅适用于某些类型的表达式,以便我们可以使用它来触发对复制/移动函数的调用,而在调用站点上使用的机器最少。