在C++中,所有的临时对象都是rvalue吗?

34

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

如果不是,能否提供一个示例,其中代码产生的临时变量是左值


1
@Prasoon,你有那个讨论的链接吗? - Rob Kennedy
2
是的,这里是链接。http://groups.google.co.in/group/comp.lang.c++/browse_thread/thread/27820909255c7217/ - Prasoon Saurav
3
我认为一个更好、更强的问题是“我能否给一个没有分配存储空间的对象分配值?” - Potatoswatter
传值函数参数是可赋值的(除非使用const),而且你不需要为它们分配存储空间。你可能需要另一种对“临时”的定义。 - MSalters
7个回答

46

不。

C++语言规范从未像你所问的那样直截了当地做出断言。在语言标准中,它并没有明确规定“所有临时对象都是右值”。此外,问题本身有点误导,因为在C++语言中,“右值”的属性并不是一个对象的属性,而是表达式的属性(即其结果的属性)。这实际上是在语言规范中如何定义的:对于不同类型的表达式,它表明结果何时是左值,何时是右值。除其他事项外,这实际上意味着临时对象可以根据用于访问的特定表达式形式作为右值和左值进行访问。

例如,文字表达式2 + 3的结果显然是一个右值,即类型为int的临时对象。我们无法将一元运算符&应用于它,因为一元运算符&要求其操作数为左值。

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

然而,正如我们所知道的那样,一个常量引用可以附加到一个临时对象上,例如

const int &ri = 2 + 3;

在这种情况下,引用被附加到临时变量上,从而延长了后者的生命周期。显然,一旦完成,我们可以将该引用作为左值ri访问同一个临时变量,因为引用始终是左值。例如,我们可以轻松合法地对引用应用一元运算符&并获得指向临时变量的指针。
const int *pi = &ri;

只要临时对象存在,指针就保持完全有效。另一个明显的临时对象的左值访问的例子是当我们通过它的“this”指针访问类类型的临时对象时。 "* this"的结果是一个左值(作为一元“*”应用于数据指针的结果始终如此),但这并不改变实际对象很容易是一个临时对象的事实。对于给定的类类型T,“T()”表达式是一个rvalue,正如语言标准中明确说明的那样,但是通过“* T().get_this()”表达式(具有“T :: get_this()” 的明显实现)访问的临时对象是一个左值。与前面的例子不同,这种方法允许您立即获得一个非const限定的左值,该左值引用临时对象。因此,再次强调,根据您使用的表达式类型(访问路径类型),同一个临时对象可能很容易被视为rvalue或lvalue。

我同意这句话:“在C++语言中,作为rvalue并不是一个对象的属性,而是表达式的属性”。但是标准并不是那么一致:5.2.3/2将T()定义为“创建指定类型的rvalue”的表达式。我的理解(英语不是我的母语)是,“rvalue”被用作所创建实例的属性,而不是表达式本身的属性。 - David Rodríguez - dribeas
在这种情况下,作者可能想表达的是rvalue由编译器管理(与lvalue相对)。 - curiousguy
为什么我们可以将const引用附加到临时对象上? - Alcott

10

Prasoon Saurav已经链接了一个非常好的clc++主题讨论。在那里,James Kanze解释了为什么这个问题实际上没有意义。归根结底是:

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

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

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

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

值为4的临时int并不是表达式。打印出来的表达式ri也不是一个临时变量,它是一个左值,并引用一个临时变量。


2
不,它不是那样工作的。表达式可能会创建临时变量或引用临时变量,但它们本身不是临时变量。它们只是不同的实体。以您的示例为例,是表达式 int(3) 是一个右值。 - MSalters
2
你在进行一种虚幻的区分。表达式除了创建和引用值之外无任何作用。表达式的求值可能会导致对临时对象的句柄。它可能导致通过句柄允许或禁止赋值。关于临时句柄是否允许赋值的问题是合法的。 - Potatoswatter
1
(1) 如果你看不到区别,那就试着构建一个编译器吧。这个区别微妙但并非虚构。 (2) 那可能是一个有效的问题,但那不是他所问的问题。为了澄清:赋值表达式的左侧是一个表达式本身,更确切地说是一个左值表达式。 - MSalters
临时变量不是表达式,但“临时”也是表达式的属性。当一个表达式首次引入一个临时变量时,那个表达式就是一个临时变量(这就是为什么规范有时会说“rvalue 临时变量”,因为 rvalue 表达式是一个临时变量)。这样可以在翻译时跟踪临时性。当然,lvalue 也可以指向临时对象,但我所知道的唯一一个 lvalue 本身是临时的点是在抛出异常时。指向临时异常对象的(合成的)lvalue 表达式是一个临时变量。 - Johannes Schaub - litb
@curiousguy - 非常有趣(也令人惊讶)的例子,但请使用::而不是:。 - Arbalest
显示剩余6条评论

1

嗯,那个数组操作符返回一个引用,任何返回引用的函数都可以被认为是做同样的事情吗?所有的引用都是const类型,虽然它们可以是左值,但它们修改的是引用指向的对象,而不是引用本身。对于 * 操作符也是如此,

*(一个临时指针) = val;

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

这样你就可以这么写:

int Afunc()
{
   return 5;
}

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


anotherFunc(Afunc());

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

int anotherFunc(const int & b);

无论如何,引用可以是lvalue和临时的,诀窍在于引用本身没有被修改,只有它所引用的内容。

如果您将->操作符视为运算符,则临时指针可以是lvalue,但同样的条件适用,不是临时指针会被更改,而是它所指向的内容。


那里的棘手之处在于你是否可以说引用是临时的。我不认为它们是临时的,但这只是一种直觉...毕竟引用具有实体(存在,占据空间),没有名称,并且将在执行语句后立即消失...相当短暂... - David Rodríguez - dribeas
2+2 会产生一个临时值吗? - David Rodríguez - dribeas
1
根据上下文而定,是的。如果作为(编译时)整数常量表达式使用,例如作为模板参数,则不是:typedef foo<2+2> FooFour - MSalters
@DavidRodríguez-dribeas "那里棘手的部分是你是否可以说引用是临时的。我不认为它们是临时的" 引用不是临时对象,但它指向临时对象。 - curiousguy
@MSalters,2+2从来不是临时的。它只是一个值。我对一些回答进行了负评,因为它们声称像2+2这样的东西是临时的。但这是没有意义的。 - Johannes Schaub - litb
显示剩余6条评论

0

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

#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)是一个右值,尽管我想将其分配。当我对其进行解引用时,它将变成左值。因此,所有临时值都是右值。但是,您可以通过解引用将右值(因此是临时值)转换为左值。


3
给定一个指针表达式 E,*E 的结果是一个左值,无论评估 E 是否产生临时值。请注意,E 的结果不是 *E!临时值不是左值,而是指向它的指针。 - David Rodríguez - dribeas
1
“_temporary lvalue_” 拥抱?没有“考虑”的余地。 (c+2+4) 根据定义是 rvalue。 - curiousguy
@curiousguy:好的。嗯。在我编辑我的答案之前,*((char*)(ptr+offset))=9是一个右值吗?如果我不解引用它,我认为这不会编译通过...好的,那么在我解引用一个右值之后,它是一个左值吗? - user34537
在C++中,*((char*)(ptr+offset))是一个左值,而*((char*)(ptr+offset))=9也是一个左值。在C语言中,只有第一个表达式是左值。"我认为如果我不解除引用它,这个代码不会编译..." (ptr+offset);将作为一个语句编译,2+2;也是如此。在C和C++中,结果被忽略的没有效果的表达式是合法的。"好的,那么当我解除引用一个右值时,它是一个左值吗?" 解除引用指针会得到一个左值。 - curiousguy
@curiousguy:非常好的回复。我知道2+2可以编译,但我的意思是(char*)2+2=0不会(rvalue被视为lvalue)。 - user34537
你不能对整数进行解引用操作! - curiousguy

0
短答案:是的,但我不会引用标准,因为证明这一点需要涉及到所有类型的临时对象。根据定义,临时对象的生命周期只有一个语句,所以将东西分配给它们最多只能算是不好的风格。
有趣的答案:复制省略可以使(通常使)临时对象与左值对象相同。例如,
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提到:

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

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


“按照定义,临时变量的生命周期为一条语句。” - curiousguy

0
数组索引操作既是一个临时值(temporary)又是一个左值(lvalue)。比如说 a[10] = 1 就是你寻找的一个例子;这个左值是一个临时的、计算出来的指针。

1
抢我一步了。数组索引始终是左值,到达那里的计算结果是临时的。 - Travis Gockel
2
我有点不同意。a[10]是CPU中的本地指令,它不会创建临时指针。 - user34537
1
表达式(假设为int数组)不会产生计算出的指针,而是通过解引用指针获得的引用。即使a[10]不是本地类型,而是用户定义的操作(a的类型是一个类,使得定义了operator[](int),例如vector),只有当它返回一个引用(所有引用都是lvalue-s)时,该操作才会编译(假设对于a[10]的类型定义了一些operator=(int))。 - David Rodríguez - dribeas
2
该表达式中没有临时对象。您调用a.operator[](int)并返回对现有对象的引用。不一定会创建任何内容。在map::operator[]的情况下,将创建永久对象,而不是临时对象。在vector<bool>::operator[]的情况下,将创建一个具有定义operator=的临时对象,但它在技术上不是左值。 - Potatoswatter
a[10] 可以在 CPU 中实现为间接寻址模式,但这并不改变计算指针的形式存在。然而,如果引用在技术上不是 lvalue...好吧,指针。 - Andrew McGregor

-2
如果没有,有人能否提供一个例子,其中在代码中产生的临时值是一个左值?
以下代码将一个常量引用绑定到编译器创建的const float类型的临时对象上:
int i;
const float &cfr = i;

这个行为就像是“好像”:

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

2
然而,这个例子并没有展示一个作为左值的临时变量。你有一个左值引用一个临时变量,但是这个临时变量(实际上是将i提升为float的表达式)是一个右值。我希望有人能比我更好地解释这个问题。 - Xeo
@Xeo:非常正确。术语“rvalue”和“lvalue”是指表达式,而不是语句,而这个答案并没有解释任何内容。临时表达式float(i)确实是一个rvalue,它被绑定到常量引用上。一个假设的表达式 cfr 将是一个 lvalue。 - Kerrek SB
@Xeo “_真的是将i转换为浮点数的表达式_” 我明白你的意思,但这是一种特殊类型的表达式,它们在源代码中没有文本(或AST)表示! - curiousguy
@Curiousguy 好的,我有点沮丧你(我猜测)的负评和在我们上次互动中所说的讽刺性评论。事实是,我诚恳地回答了足够多的新手问题(例如:http://stackoverflow.com/questions/8352139/how-to-populate-a-listbox-in-c-with-all-folders-in-a-directory/8353430#8353430),因此我认为任何拥有“真正知识”的人都应该以更高的交流标准来要求。但这看起来非常令人沮丧,我向您提供一份圣诞+1作为善意的姿态,并希望对于知识和互联网的未来——尽管它们有时可能是匿名的——充满希望。 :-/ - HostileFork says dont trust SE
@HostileFork “我感到沮丧”,我也因为我们无法沟通而感到沮丧。“我认为是一条讽刺的评论” 哪个评论? ;) “一种善意的姿态” 谢谢,我很感激。 - curiousguy
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接