【问题标题】:Rvalue Reference is Treated as an Lvalue?右值引用被视为左值?
【发布时间】:2015-04-13 12:43:03
【问题描述】:

我发布了这个答案:https://stackoverflow.com/a/28459180/2642059 其中包含以下代码:

void foo(string&& bar){
    string* temp = &bar;

    cout << *temp << " @:" << temp << endl;
}

bar 是右值还是左值?

我问是因为我显然不能获取右值的地址,但我可以像这里所做的那样获取右值引用的地址。

如果您可以对右值引用执行任何操作,而您可以对左值引用执行任何操作,那么用“&&”而不是“&”区分两者有什么意义?

【问题讨论】:

  • 右值的全部意义在于允许移动语义,它们在大多数情况下(除了它们的类型)只是引用。
  • 在您的示例中,您没有移动两次,而是复制了两次,因为 bar 是一个左值(它的类型是一个右值,我知道很奇怪)。但是,如果您两次致电std::move,您可能会遇到问题。
  • @JonathanMee 而这种对bar 的双重使用正是为什么bar 是一个左值——所以这种双重使用不会给你带来问题。只有当您明确执行此操作时,它才会被视为右值:std::move(bar)
  • @JonathanMee:你想错了。右值引用被移动进入而不是移出。你知道当你的函数被调用时bar 会被销毁,这就是你得到的。但是,如果您随后在您的内部调用一个函数,除非您明确说明,否则您不会做出该保证。
  • “那我们为什么称它为右值引用呢?” - 因为它是对右值的引用(或者迂腐地,对由右值表达式表示的对象)。这意味着它指的是一个临时变量,或者一个被显式移出的变量(使用 std::move 或等效项),因此您的函数可以假设可以从它移出。

标签: c++ c++11 rvalue-reference lvalue rvalue


【解决方案1】:

bar 是右值还是左值?

问题自己回答。任何有名字的都是左值(1)。所以bar 是一个左值。它的 type 是“对string 的右值引用”,但它是该类型的左值。

如果要将其视为右值,则需要对其应用std::move()


如果您可以对右值引用执行任何操作,而您可以对左值引用执行任何操作,那么用“&&”而不是“&”区分两者有什么意义?

这取决于您对“执行操作”的定义。左值引用和(命名的)右值引用在表达式中使用它们的方式几乎相同,但它们在可以绑定到它们的内容上很多。左值可以绑定到左值引用,右值可以绑定到右值引用(任何东西都可以绑定到对const 的左值引用)。也就是说,您不能将右值绑定到左值引用,反之亦然。

我们来说说一个右值引用类型的函数参数(比如你的bar)。重要的不是bar 是什么,而是你对bar 所指的值的了解程度。由于bar 是一个右值引用,您可以肯定地知道绑定到它的任何内容都是一个右值。这意味着当完整表达式结束时它必然会被销毁,您可以放心地将其视为右值(通过窃取其资源等)。

如果您不是直接向bar 执行此操作的人,而只是想传递bar,您有两种选择:要么您已完成bar,然后您应该告诉下一个接收到它绑定到右值的人—做std::move(bar)。或者你需要用bar做更多的事情,所以你不希望任何人从你下面窃取它的资源,所以把它当作一个左值——bar

总结一下:区别不在于你可以用引用做什么一旦你拥有它。区别在于可以绑定到什么引用.


(1) 一个很好的经验法则,除了少数例外:枚举数有名字,但是是右值。类、命名空间和类模板有名称,但不是值。

【讨论】:

  • @Agnew 这会触发我问题的第二部分,那么将其称为右值引用有什么意义呢?为什么不直接调用右值和左值引用:references?
  • @JonathanMee 试图解释。如果它更清楚或需要更多,请告诉我。
  • @JonathanMee 它是对可以安全地视为右值的东西的引用(也就是说,您可以在其上调用std::move,并且您不会冒险使可能预期的对象无效其他地方保持有效)。
  • @JonathanMee 不,这不仅仅是对程序员的提示。这是外部的硬性规则不同之处不在于拥有参考后可以做什么。不同之处在于什么可以绑定到引用。
  • “有名字的就是左值。” 迂腐的评论:枚举数是右值。有没有一个简单的规则来调和这一点? (例如“变量和函数的名称是左值”)
【解决方案2】:

bar 是 rvalue 还是 lvalue

它是一个左值,就像任何命名变量的表达式一样。

如果您可以对 rvalue 引用执行任何操作,而您可以对 lvalue 引用执行任何操作,那么使用“&&”而不是区分两者的意义何在只是一个“&”?

您只能使用 rvalue 表达式初始化 rvalue 引用。因此,您可以将临时字符串传递给您的函数,但您不能在不显式移动的情况下传递字符串变量。这意味着您的函数可以假定不再需要该参数,并且可以从该参数中移出。

std::string variable;
foo(variable);            // ERROR, can't pass an lvalue
foo(std::move(variable)); // OK, can explicitly move
foo("Hello");             // OK, can move from a temporary

【讨论】:

  • 所以你说右值引用的目的是通知编译器当它在构造函数中使用该值时,它可以使用移动构造函数?如果我初始化两次,这不会引起问题吗?说string temp1(bar), temp2(bar);
  • @JonathanMee:不,右值引用的要点是它只能绑定到右值。在该示例中,bar 是一个左值,因此两个temp 变量都使用复制构造函数(采用左值引用)而不是移动构造函数(采用右值引用)进行初始化 - 防止如果第一个构造函数从 bar 移出。
【解决方案3】:

表达式bar 是一个左值。但它不是“对字符串的右值引用”。表达式bar 是一个左值引用。您可以通过在 foo() 中添加以下行来验证它:

cout << is_lvalue_reference<decltype((bar))>::value << endl; // prints "1" 

但我同意Angew的其余解释。

一个右值引用,在它被绑定到一个右值之后,就是一个左值引用。其实不只是函数参数:

string&& a = "some string";
string&& b = a; // ERROR: it does not compile because a is not an rvalue

如果您可以对右值引用执行任何操作,而您可以对左值引用执行任何操作,那么用“&&”而不是“&”区分两者有什么意义?

右值引用允许我们在右值到期之前执行一些“左值操作”。

【讨论】:

  • 要明确:说“酒吧的类型”是模棱两可的。标记bar 在语法上可以是表达式,也可以不是表达式。如果它在语法上是表达式,则类型为std::string(不是引用)。但是,如果它在语法上是 decltype(id-expression) 的操作数,则类型为 std::string&amp;&amp;
【解决方案4】:

在这种情况下 命名右值引用是左值,而如果输入类型是 const 命名右值引用 那么就是rvalue,这里是一个简单的例子:

#include <string>
#include <iostream>

void foo(const std::string&& bar) { 
    std::string* temp = &bar; // compile error, can't get address
    std::cout << *temp << " @:" << temp << std::endl;
}

int main()
{
    foo("test");
    return 0;
}

希望这是有用的。

【讨论】:

  • 所以这似乎是真的。你能提供这方面的来源吗?
  • 这是错误的。 bar 在这里仍然是左值;问题是&amp;bar 的类型为const std::string *,因此不能将其分配给std::string*,这将丢弃const 限定符。如果您将代码更改为const std::string* temp =,那么它会编译得很好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-17
  • 2011-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多