为什么不能将临时对象绑定到非const引用?

68
C++允许将临时对象赋值给const引用,但不允许将临时对象赋值给引用。
例如:
String& a = String("test");         // Error
const String& a = String("test");   // Ok

无论我在哪里搜索这个结果,我只看到以下答案:
1. 修改临时对象会导致无法识别的问题 2. 修改临时对象是危险的 3. 在某个时间点,你会忘记它是一个临时变量
据说,临时对象在语句之后消失。所以你不应该修改它。
如果C++如此热衷于阻止修改临时对象,那它应该阻止读取临时对象,对吗?如果临时对象消失了,那么从那里读取内容就没有意义了,对吗?可能发生正确的情况也可能涉及到读取。
那么为什么它只阻止写入而允许读取呢?
请给我一个扎实的C++代码解释。
请不要偏离问题,指出一些替代方案。请给我一个扎实的答案,用代码解释为什么对于临时对象,允许const int &,而不允许int &。
有人说有&&..我的问题不同.. 还有人说,改变不会反映出来..即使是const int &,改变也不会影响i..例如:double a; const int & i = a; a++; 不会影响i..

我不确定我理解这个问题。这个链接算作从临时读取吗?如果你说的是传递给函数的临时对象,你需要复制它、移动它或使用一个const引用,来延长其生命周期。 - chris
3
+1 我也一直想知道这个。 - user541686
我认为对于编译器的作者来说,分析对象的所有引用以提供任何保证是非常困难的。 - Dmytro Sirenko
1
@EarlGray:是的,那么const和非const临时变量有什么不同呢? - user541686
1
еӣ дёәC++дјҡ延й•ҝз»‘е®ҡеҲ°ж ҲдёҠзҡ„constеј•з”Ёзҡ„дёҙж—¶еҸҳйҮҸзҡ„з”ҹе‘Ҫе‘ЁжңҹгҖӮиҜ·еҸӮи§ҒжӯӨеӨ„зҡ„жңҖдҪізӯ”жЎҲпјҡhttps://dev59.com/questions/5nE85IYBdhLWcg3wbS1h - Chad
显示剩余2条评论
4个回答

40

不允许引用临时对象的最初原因是函数参数。假设允许这样:

void inc(double& x)
{ x += 0.1; }

int i = 0;
inc(i);

为什么i没有改变?


2
这个在编译时不会出错吗?(难道不会吗?)我不确定问题出在哪里... - user541686
15
现在它失败了,如果允许绑定到临时变量,它就不会失败。整数在传递给函数之前被转换为临时双精度浮点数。 - Bo Persson
3
嗯,有趣...但他们不能说“如果值已经是左值引用,则不允许将其隐式转换为另一种左值引用类型”吗?或许那样会失去意义...不确定,还在考虑...+1 - user541686
2
@Andrew,那个怎么解释呢?i无法被增加,因为该函数只接受double&而非int&。所以,从i中获取一个被转换构造的临时double,这就是被改变的内容。改变的大小完全无关紧要:当调用被执行时,i已经不在图片中了。实际上,我很高兴C++不允许这样做,否则我们会得到所有这些问题:“但我增加了整数,所以应该可以工作,对吧?”这听起来像你在说什么。但也许我错过了什么。 - underscore_d
5
这个例子说明允许隐式转换并不是一个好主意。 - user7860670
显示剩余6条评论

21
如果C++非常热衷于阻止修改临时对象,那应该阻止读取临时对象,不是吗?如果临时对象消失了,那么从那里读取内容就没有意义了,对吗?
不,读取对象是完全合理的。仅因其将来会消失并不意味着现在读取数据是无意义的。
open_file(std::string("foo.txt"));

std::string("foo.txt")是一个临时对象,在调用open_file()后将停止存在,但在其存在期间所包含的数据非常重要。

不允许临时对象绑定到非const引用的原因实际上并不是修改临时对象存在根本性问题。事实上,在许多地方,C++都可以愉快地修改临时对象:

std::string("foo") = "bar";

只是设计师们认为如果不禁止临时变量绑定到非const引用,可能会引起足够多的问题(可能是由于“out参数”的常见习语),而没有启用类似价值的任何内容,因此他们简单地做出了一个设计决策来禁止这样做。

现在有了右值引用,您可以做之前被禁止的事情:

void foo(int &&output) {
    output = 1;
}

foo(2);

这个可以正常工作,只是没有太多用处。


12
谢谢提及。不允许将临时对象绑定到非const引用的原因实际上并不是写入临时对象存在某些根本性问题。 - rekli

7
如果您有一个非常昂贵的临时对象需要拷贝,您可能更愿意采用对该对象的const&引用(例如函数返回),而不是将其复制到另一个变量中以便以后使用。将常量引用应用于临时对象可以扩展该临时对象的生存周期,使其与引用的生命周期一样长,从而允许您访问任何可读状态。
不允许编写代码,因为一旦想要改变一个变量,您最好使用一个真正的实例,而不是仅作为非const引用别名的临时对象。

1
将一个常量引用指向一个临时变量可以延长该临时变量的生命周期,直到引用的生命周期结束。但是,这只适用于传递实际对象,而不是例如另一个常量引用。而且它并不能解释为什么非常量引用不能做同样的事情。 - user541686
1
@Mehrdad,问题似乎明确说明了需要对实际的临时对象进行const引用。不过我可能误读了OP的意图。 - Mark B
1
一旦您想要改变一个变量,最好使用真实的实例。 - underscore_d

1

这其中有一个合乎逻辑的原因。想一想,你实际上想在这行中得到什么:

String& a = String("test");         // Error

你需要一个引用。引用与其所引用的对象相关联。就像对象的地址一样(虽然引用不是地址,但这样解释更清楚)。你实际上想要获取类似于String("test")的地址。但是该对象将在下一行消失,那么如果它指向的对象不存在,那么它的地址有什么意义呢?a现在指向了一些毫无意义的东西...

关于你的第二个问题,允许临时对象的意义是什么,好吧,这没有什么问题。例如,考虑这样一种情况:你想将一个String对象传递给一个函数,该函数返回与该字符串对应的修改后的字符串。我们称该函数为DoubleString,因此不需要执行以下操作:

String s("hello ");
String s2 = DoubleString(s);

您可以使用更短、更方便的表单。

String s2 = DoubleString(String("hello "));

看,临时对象 String("hello ") 存活于整个代码行,这意味着它在传递给 DoubleString 之前和之后都是完整的。它仅在整个行完成时才被销毁。


2
第一个例子不相关。你可以将它变成 const String& a = ...,由于“生命周期延长”,它仍然可以工作。如果允许非 const 绑定,则假定生命周期延长也适用于它。 - ndkrempel
1
(例如:生命周期延长已经适用于String&& a = ...,并且在您当前的示例中也可以工作。) - ndkrempel

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