【问题标题】:Are all temporaries rvalues in C++?C ++中的所有临时右值吗?
【发布时间】:2011-01-09 20:14:21
【问题描述】:

过去几年我一直在使用 C++ 进行编码。但是有一个问题我还没有弄清楚。我想问一下,C++中的临时变量,都是右值吗?

如果不是,谁能给我一个例子,其中代码中的临时生成是一个左值

【问题讨论】:

  • 有那个讨论的链接,@Prasoon?
  • 我认为一个更好、更强大的问题是“我可以为一个我没有分配存储空间的对象分配一个值吗?”
  • 按值传递函数参数是可分配的(除非是 const),您不需要为它们分配存储空间。您可能想要“临时”的另一个定义。

标签: c++ rvalue temporaries


【解决方案1】:

没有。

C++ 语言规范从未像您所询问的那样做出如此直接的断言。语言标准中的任何地方都没有说“所有临时对象都是右值”。此外,这个问题本身有点用词不当,因为在 C++ 语言中作为右值的属性不是对象的属性,而是表达式的属性(即其结果的属性)。这实际上是它在语言规范中的定义:对于不同类型的表达式,它表示结果是左值和右值。除此之外,这实际上意味着临时对象可以作为右值和左值进行访问,具体取决于用于执行访问的特定表达式形式。

例如,文字2 + 3 表达式的结果显然是一个右值,一个临时的int 类型。我们不能将一元 & 应用于它,因为一元 & 需要一个左值作为其操作数

&(2 + 3); // ERROR, lvalue required

然而,众所周知,一个常量引用可以附加到一个临时对象上,如

const int &ri = 2 + 3;

在这种情况下,引用附加到临时对象,从而延长后者的生命周期。显然,一旦完成,我们就可以访问与左值 ri 相同的临时值,因为引用始终是左值。例如,我们可以轻松合法地将一元 & 应用于引用并获得指向临时的指针

const int *pi = &ri;

只要临时仍然存在,该指针就会保持完全有效。

另一个左值访问临时对象的明显例子是当我们通过它的this指针访问一个类类型的临时对象时。 *this 的结果是一个左值(就像一元 * 应用于数据指针的结果一样),但它并没有改变实际对象可能很容易成为临时对象的事实。对于给定的类类型T,表达式T() 是一个右值,正如语言标准中明确规定的那样,但通过*T().get_this() 表达式访问的临时对象(T::get_this() 的明显实现)是一个左值。与前面的示例不同,此方法允许您立即获得一个非 const 限定的左值,它引用一个临时对象。

因此,同样的临时对象可能很容易被“视为”为右值或左值,具体取决于您用于“看”那个物体。

【讨论】:

  • 我同意这句话:'作为 C++ 语言中的右值不是对象的属性,而是表达式的属性'。但话又说回来,标准并不太一致:5.2.3/2 将T() 定义为“创建指定类型的右值”的表达式。我的理解(英语不是我的母语)是“右值”用作创建实例的属性,而不是表达式本身。
  • @DavidRodríguez-dribeas 在这种情况下,作者可能想表达一个事实,即右值由编译器管理(而不是左值)。
  • 为什么我们可以将 const 引用附加到临时对象?
【解决方案2】:

Prasoon Saurav 已经链接了一个非常好的 clc++ 线程。在那里,James Kanze 解释了为什么这个问题没有真正的意义。归结为:

  • rvalue-ness 是表达式的(布尔)属性 - 每个表达式都是左值或右值
  • 临时变量是不是表达式

因此,这个问题没有意义。

下面的代码就是一个很好的例子:

int main() {
  const int& ri = 4;
  std::cout << ri << std::endl; 
}

值为4 的临时int 不是表达式。打印的表达式ri 不是临时的。它是一个左值,指的是一个临时值。

【讨论】:

  • 表达式可能是临时的。例如,int(3) = 5 是非法的,但如果 int(3) 创建的临时值不需要只是右值,则它是合法的。
  • 不,这样不行。表达式可能创建临时对象,或引用临时对象,但它们本身不是临时对象。他们只是不同的野兽。举个例子,它是 expression int(3) 这是一个右值。
  • 你在做一个想象中的区别。表达式除了创建和引用值之外什么都不做。表达式的评估可能会导致临时对象的句柄。它可能会产生一个句柄,通过该句柄允许或禁止分配。临时句柄是否允许赋值的问题是有效的。
  • (1) 抱歉,如果您看不到区别,请尝试构建编译器。区别是微妙的,但不是想象的。 (2) 这可能是一个有效的问题,但这不是他问的问题。并且澄清一下:赋值表达式的 LHS 本身就是一个表达式,准确地说是一个左值表达式。
  • (1) 将原始文本转换为 AST 后,(设计良好的)编译器会处理值图。如果你想将一段数据从语法传递给代码生成器,你最好找到一个值或控制流结构来挂住它。 (2) 正如我所说,一个表达式可以产生一个允许赋值的对象的句柄。当前范围内临时对象的句柄不允许赋值,尽管(这是我之前的错误)一些左值是 const 并且不允许赋值。
【解决方案3】:

好吧,那个数组运算符返回一个引用,任何返回引用的函数都可以被认为做同样的事情吗?所有引用都是 const,虽然它们可以是左值,但它们会修改引用的内容,而不是引用本身。 *operator 也是如此,

*(a temp pointer) = val;

我发誓我曾经使用过一些编译器,它会将临​​时值传递给任何接受引用的函数,

所以你可以去:

int Afunc()
{
   return 5;
}

int anotherFunc(int & b)
{
    b = 34;
}


anotherFunc(Afunc());

虽然现在找不到可以让您这样做的,但引用必须是 const 才能允许传递临时值。

int anotherFunc(const int & b);

无论如何,引用可以是左值和临时的,诀窍是它自身的引用没有被修改,只有它引用的内容。

如果将-&gt; 运算符算作运算符,那么临时指针可以是左值,但同样的条件适用,它不是要更改的临时指针,而是它指向的东西。

【讨论】:

  • 棘手的部分是您是否可以说引用是临时的。我不会认为它们是临时的,但这只是一种直觉......毕竟引用具有实体(它存在,占用空间),未命名并且将在执行句子后立即消失...... . 相当暂时的......
  • @David:唯一的问题是“临时”是否被视为“临时对象”的简写。临时对象由 §12.2 描述,不包括引用。
  • @David:写入 RAM 无关紧要。但是,引用比 RAM 或寄存器更进一步,因为它们确实不应该像真正的对象那样存在。 “引用是所指对象”或者只是对象的名称,可能是在底层使用指针实现的,但没有构造,甚至没有 2+2 具有的显式生命周期。
  • 2+2 会产生一个临时的吗?
  • 根据上下文,是的。如果用作(编译时)整数常量表达式,则不是,例如作为模板参数:typedef foo&lt;2+2&gt; FooFour
【解决方案4】:

数组索引操作既是临时操作又是左值,例如 a[10] = 1 就是您要查找的示例;左值是一个临时的计算指针。

【讨论】:

  • 打败我。数组索引始终是左值,到达那里的计算结果是临时的。
  • 我不同意。 a[10] 是 CPU 中的本机指令。它不会创建临时指针。
  • 表达式(假设是一个 int 数组)不会产生一个计算的指针,而是一个通过取消引用指针获得的引用。即使a[10]不是原生类型,而是用户定义的操作(a的类型是定义operator[](int)的类,比如vector),只要它返回一个引用(所有引用都是左值) -s) 操作将编译(假设为a[10] 类型定义了一些operator=(int)
  • 该表达式中没有临时对象。您调用a.operator[](int),它会返回对现有对象的引用。没有什么是必然被创造出来的。在map::operator[] 的情况下,会创建一个永久 对象,而不是临时对象。在vector&lt;bool&gt;::operator[] 的情况下,会创建一个定义了operator= 的临时对象,但从技术上讲它不是左值。
  • a[10] 可以在 CPU 中实现为间接寻址模式,但这不会改变计算指针的形式存在。但是,如果引用在技术上不是左值......好吧,好吧,点。
【解决方案5】:

简短的回答:是的,但我不会引用标准,因为证明这一点需要解决存在的各种临时问题。根据定义,临时语句的生命周期只有一个语句,因此将事物分配给一个语句充其量是一种糟糕的风格。

有趣的答案:复制省略可以使(通常使)一个与左值对象相同的临时对象。例如,

MyClass blah = MyClass( 3 ); // temporary likely to be optimized out

return MyClass( 3 ); // likely to directly initialize object in caller's frame

编辑:至于在这些情况下是否有任何临时对象的问题,§12.8/15 提到

可以通过将临时对象直接构造到省略复制的目标中来省略复制操作

这表明存在一个可能与左值相同的临时对象。

【讨论】:

  • "根据定义,临时的生命周期为一条语句"?
【解决方案6】:

这取决于您认为临时变量是什么。你可以写类似

#include <stdio.h>
int main()
{
    char carray[10];
    char *c=carray+1;
    *(c+2+4) = 9;
    printf("%d\n",carray[7]);
    return 0;
}

这在 VisualStudios 和 GCC 中运行。可以在codepad中运行代码

我认为 (c+2+4) 是一个右值,尽管我想分配给它。当我取消引用它时,它将成为一个左值。所以是的,所有临时变量都是右值。但是你可以通过取消引用将右值(因此是临时的)变成左值

【讨论】:

  • 给定一个指针表达式 E,*E 的结果是一个左值,不管 E 的求值是否产生一个临时值。注意 E 的结果不是 *E!!临时不是左值,而是指向它的指针。
  • 临时左值”拥抱?没有“考虑”的余地。根据定义,(c+2+4) 是一个右值。
  • @curiousguy:对。好的。唔。在我编辑之前,我的答案是*((char*)(ptr+offset))=9 一个右值?我认为如果我不尊重它,这不会编译...好吧,所以在我尊重一个右值之后,它是一个左值吗?
  • 在 C++ 中,*((char*)(ptr+offset)) 是左值,*((char*)(ptr+offset))=9 也是。在 C 中,只有第一个表达式是左值。 “如果我不尊重它,我认为这不会编译...(ptr+offset); 将编译为语句,2+2; 也将编译为语句。结果也被忽略的无效表达式在 C 和 C++ 中是合法的。 “好的,所以在我尊重一个右值之后它是一个左值吗?” 取消引用一个指针会让你得到一个左值。
  • @curiousguy:很好的回复。我知道 2+2 编译,但我的意思是 (char*)2+2= 0 不会(右值被视为左值)
【解决方案7】:

如果没有,谁能给我一个例子,代码中临时生成的是左值吗?

以下代码将常量引用绑定到由编译器创建的const float 类型的临时对象:

int i;
const float &cfr = i;

行为是“好像”:

int i;
const float __tmp_cfr = i; // introduced by the compiler
const float &cfr = __tmp_cfr;

【讨论】:

  • 不过,这个例子没有显示一个临时的左值。你有一个左值 referring 到一个临时值,但这个临时值(实际上是将i 提升为float 的表达式)是一个右值。我希望有人能比我更好地解释这一点。
  • @Xeo:完全正确。术语“右值”和“左值”指的是表达式,而不是语句,这个答案并没有解释任何这些。临时表达式float(i) 确实是一个右值,它被绑定到常量引用。假设的 表达式 cfr 将是一个左值。
  • @Xeo "真的是提升 i 浮动的表达" 我明白你的意思,但这是一种特殊的表达方式,没有文本(或 AST ) 表示出现在源代码中!
  • @KerrekSB "这个答案没有解释任何这些" MSalters 已经在他的回答中解释说这个问题没有真正的意义,所以我觉得我没有不得不重复这一点。 “临时表达式float(i)”但是这个表达式并没有出现在代码片段中,那我们怎么能称它为:虚拟表达式呢?
  • MSalters 已经在他的回答中解释说这个问题没有真正的意义”字面意思,它没有;但我想我知道要问什么。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-14
  • 2014-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多