错误:从类型为'int'的右值无效初始化了'int&'类型的非常量引用


87

格式错误:

int &z = 12;

正确形式:

int y;
int &r = y;

问题
为什么第一个代码错误?标题错误的“含义”是什么?


4
临时不能绑定到非恒定引用。在这种情况下,int(12)是临时的。
Prasoon Saurav

@PrasoonSaurav临时12是什么意思?此处缺乏概念(对我而言:))
Aquarius_Girl

2
请注意,对此限制没有严格的技术原因。允许对临时变量的可变引用将很容易实现。禁止这样做是C ++的设计决定,因为这样的构造将是较差的设计,比真正的实用程序有更大的被无意滥用的风险。(我只有一次发现这样的事情做作的需要。)
Kerrek SB

@KerrekSB绑定引用到右值对象的最常见需求可能是(ostringstream() << "x=" << x).str()
curiousguy 2011年

@curiousguy:是的,这就是我发布的链接的内容。
Kerrek SB 2011

Answers:


132

C ++ 03 3.10 / 1说:“每个表达式都是左值或右值。” 重要的是要记住,左值与右值是表达式的属性,而不是对象的属性。

左值命名持久存在于单个表达式之外的对象。例如obj*ptrptr[index],和++x都是左值。

右值是临时值,它们在它们所居住的完整表达式的结尾(“在分号处”)消失。例如1729x + ystd::string("meow"),和x++都是右值。

地址运算符要求其“操作数应为左值”。如果我们可以接受一个表达式的地址,则该表达式为左值,否则为右值。

 &obj; //  valid
 &12;  //invalid

2
如果我们可以接受一个表达式的地址,则该表达式为左值,否则为右值。 ”如果只有C ++这么简单!(但是这里的细微差别并不真正相关)“右值是临时的”临时值是什么?对象?
curiousguy

2
@curiousguyRvalues是临时变量,它们在它们所居住的完整表达式的末尾消失。为了简单回答为什么expressint & = 12;无效,标准说字符串文字是左值,其他文字是右值。
BruceAdi

@curiousguy:是的。右值是临时对象,但并非所有右值都是临时对象。有些甚至不是对象。
纳瓦兹

例如,1729,x + y,std :: string(“ meow”)和x ++都是右值 ”但std::string("meow")构造了一个类型的对象,std::string并产生了一个右值,该右值指定了该对象,1729没有副作用,并且产生了值1729作为类型的右值int
curiousguy

1
@curiousguy:该语句"Lvalues name objects that persist beyond a single expression."是100%正确的语句。相反,您的示例(const int &)1是错误的,因为它不是“命名”对象。
纳瓦兹

53
int &z = 12;

在右侧,int从整数文字创建了一个临时类型的对象12,但是该临时对象不能绑定到非const引用。因此,错误。与以下内容相同:

int &z = int(12); //still same error

为什么创建一个临时文件?因为引用必须引用内存中的对象,并且要存在对象,所以必须先创建引用。由于该对象未命名,因此它是一个临时对象。它没有名字。从这个解释中,很清楚为什么第二种情况很好。

可以将一个临时对象绑定到const引用,这意味着您可以执行以下操作:

const int &z = 12; //ok

C ++ 11和Rvalue参考:

为了完整起见,我想补充一点,C ++ 11引入了rvalue-reference,它可以绑定到临时对象。因此,在C ++ 11中,您可以这样编写:

int && z = 12; //C+11 only 

请注意,其中有&&intead &。还要注意const,即使z绑定到的对象是由integer-literal创建的临时对象,也不再需要该对象12

由于C ++ 11引入了rvalue-referenceint&因此现在称为lvalue-reference


10

12是一个编译时常量,与所引用的数据不同,它不能更改int&。你可以做的是

const int& z = 12;

2
@curiousguy,请保持一致,您说过“您必须了解这些是C ++规则。它们存在,不需要任何理由”(更不用说第一版了)。我该如何解释您的抱怨,即没有根据自己的意愿付出什么?
Michael Krelin-黑客

2
@ MichaelKrelin-hacker:从技术上讲,您不能(永远)将引用绑定到值(或编译时间常数),对于实际发生的情况,该标准非常明确:否则,将创建“ cv1 T1”类型的临时项,使用非引用复制初始化(8.5)的规则从初始化程序表达式进行初始化。然后,将引用绑定到临时对象。即,允许使用语法,但是语义不是将引用绑定到常量的语义,而是将其绑定到隐式创建的临时对象的语义。
大卫·罗德里格斯(DavidRodríguez)-dribeas

1
@curiousguy:语言的规则是设计的一部分,而且经常有理由说明为什么这样设计语言。在这种特殊情况下,就像在C语言中一样,不允许您使用值的地址(它没有一个),也不能绑定引用。现在考虑一个函数void f( vector<int> const & ),它是惯用的,可以传递不被修改的向量。现在的问题是那f( vector<int>(5) )将是不正确的,并且用户将不得不提供不同的过载void f( vector<int> v ) { f(v); },这是微不足道的。
大卫·罗德里格斯(DavidRodríguez)-dribeas

1
...现在,因为那会使该语言的用户感到痛苦,设计人员决定编译器将为您执行等效的操作,在调用中f( vector<int>(5) ),编译器创建一个临时文件,然后将引用绑定到该临时文件,并且类似地如果5直接进行了隐式转换。这允许编译器为功能生成单个签名,并启用功能的单个用户实现。从那以后,为保持一致性,对于常量引用的其余用法定义了类似的行为。
大卫·罗德里格斯(DavidRodríguez)-dribeas

1
@ MichaelKrelin-hacker:引用是对象的别名,值不是对象。根据上下文的不同,引用可以只是别名,编译器将删除引用并仅使用标识符来表示原始对象的含义(T const & r = *ptr;r该函数中以后对的任何使用都可以替换为*ptr,并且r在运行时不需要存在)或者可能必须通过保留其别名的对象地址来实现(考虑将引用存储为对象的成员),该地址是作为自动取消引用的指针实现的。
大卫·罗德里格斯(DavidRodríguez)-dribeas

4

非const和const引用绑定遵循不同的规则

这些是C ++语言的规则:

  • 由文字数字(12)组成的表达式是“右值”
  • 这是不允许创建具有右值非const引用:int &ri = 12;是非法的构造
  • 允许创建一个带有右值的const引用:在这种情况下,编译器会创建一个未命名的对象;只要引用本身存在,此对象就将保留。

您必须了解这些是C ++规则。他们就是。

用稍微不同的规则发明另一种语言(例如C ++)很容易。在C ++中,将允许创建带有右值的非常量引用。这里没有矛盾或不可能的事情。

但这会允许一些风险较大的代码,使程序员可能无法获得预期的结果,因此C ++设计人员正确地决定避免这种风险。


0

引用是“隐藏的指针”(非空),指向可以更改的事物(左值)。您不能将它们定义为常数。它应该是“可变”的东西。

编辑::

我在想

int &x = y;

几乎等于

int* __px = &y;
#define x (*__px)

其中__px是一个新名称,并且#define x仅在包含x引用声明的块内起作用。


1
为什么可以,如果引用是const:)
Michael Krelin-黑客

但是海报的例子不是const
Basile Starynkevitch

是的,我的意思是您的措辞“引用是指向可以更改的事物的指针”-这是关于非成本引用的。
Michael Krelin-黑客

引用是“隐藏的指针” “错误”可以更改的事物“错误”可以更改的事物(左值)。 “错误
curiousguy

@Loki:您能解释更多吗?
Basile Starynkevitch
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.