了解左值/右值表达式与对象类型
Understanding lvalue/rvalue expression vs object type
我已经阅读了一些先前的热门答案以及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());
}
明白为什么如果你有一个右值引用类型参数,你需要通过
std::move()
实际将其转换为右值以使其有资格被移动。
正如引号所说,类型和值类别是不同的东西。参数始终是左值,即使其类型是右值引用;我们必须使用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
}
存在RValue 引用来解决转发问题。C++中现有的类型推导规则使得不可能具有一致且合理的移动语义。因此,类型系统得到了扩展,并引入了新的规则,使其更加复杂但一致。
只有当你从正在解决的问题的角度来看待它时,这才有意义。这里有一个很好的链接,专门用于解释 RValue 参考。
我认为您实际上已经掌握了类型和值类别之间的区别,因此我将重点介绍两个特定的声明/查询:
您需要通过 std::move() 实际将其转换为右值,以使其有资格被移动
有点,但不是真的。将命名或引用对象的表达式强制转换为右值允许我们在重载解析期间触发函数重载Type&&
.按照惯例,当我们想要转让所有权时,我们会这样做,但这并不完全等同于使其"有资格被移动",因为移动可能不是您最终要做的事情。从某种意义上说,这是一种吹毛求疵,尽管我认为理解这一点很重要。因为:
即使参数(所有参数)是左值,编译器也知道其类型是右值引用,那么为什么需要告诉编译器它可以移动呢?
除非你写std::move(theThing)
,或者这个东西是一个临时的(已经是一个右值),那么它就不是右值,所以它不能绑定到右值引用。这就是一切的设计和定义方式。它是故意这样做的,以便左值表达式(命名事物的表达式)或您没有std::move()
编写的事物不会绑定到右值引用。因此,要么你的程序不会编译,要么,如果可用,重载解析将选择一个可能需要const Type&
的函数版本——我们知道这不会涉及所有权转让。
tl;DR:编译器不知道它的类型是右值引用,因为它不是。就像你不能做int& ref = 42
一样,你不能做int x = 42; int&& ref = x;
。否则,它会尝试移动所有东西!重点是使某些类型的引用仅适用于某些类型的表达式,以便我们可以使用它来触发对复制/移动函数的调用,以在调用站点使用最少的机器。
- 有没有办法一次声明相同类型的多个对象,并通过一个表达式立即使用相同的右值初始化它们?
- 我可以制作矢量对象的算术表达式吗?
- 为什么带有指针子对象的文字类类型的 constexpr 表达式不能是非类型模板参数
- 子表达式中临时对象的生存期
- 如何使用条件表达式返回对象指针?
- 在C++中,当表达式涉及对象时,将表达式赋值到对象中时,是否有定义的操作顺序?
- 与基类子对象相关的表达式的动态类型
- 删除未使用新表达式构建的对象实际上可以吗?
- 使用折叠表达式构造一个平凡的对象
- 表达式必须具有指向对象的指针类型(指针向量)
- 表达式必须在C 中具有指针对象类型
- 下标需要数组或指针类型表达式必须具有指针对象类型
- 该表达式必须具有对象指针类型
- 如何将新的 lambda 表达式分配给函数对象?
- 更改对象并将其在同一表达式中使用它是不确定的行为,但是子表达式由逗号运算符分开
- 指针对象使用CPP中的指针表达式进行比较
- 了解左值/右值表达式与对象类型
- 删除时出错:表达式必须是指向完整对象类型的指针
- 表达式必须是指向完整对象类型C++的指针
- 为什么在对象表达式的上下文中找不到我的构造函数