常量引用和左值

19

我们不能写成int& ref = 40,因为我们需要右边的lvalue。但是我们可以写const int& ref = 40。为什么呢?因为40是一个rvalue而不是一个lvalue

我知道这是一个例外,但是为什么呢?

4个回答

21
如Stroustrup所说:
const T的初始化程序不需要是一个左值,甚至不需要是T类型。在这种情况下:
1. 如果必要,首先应用隐式类型转换到T。 2. 然后将结果值放入类型为T的临时变量中。 3. 最后,该临时变量被用作初始化程序的值。
因此,当您输入"const int& ref = 40"时,临时int变量会在幕后创建,并且ref绑定到这个临时变量。

16

在该语言中有一条规则允许将常量左值引用绑定到右值。这个规则的主要原因是,如果没有它,那么你将不得不提供不同的函数重载来使用临时变量作为参数:

class T; // defined somewhere
T f();
void g(T const &x);

有了这个规则,你可以执行g(f()),如果没有这个规则,你需要创建一个不同的g重载函数来接受一个rvalue(而这是在rvalue引用还不存在于语言中的时候!)。


我仍然不确定为什么生命周期被延长,虽然 :-( - Kerrek SB
@KerrekSB: 我猜最初的原因是这样你可以写 T const& _ = ...; 或者 T const _ = ...; 而不必在意哪种方式会起作用,前者在表达式生成引用时更为高效(没有副本)。然而,这确实非常不幸,因为它会让很多错误通过。 - Matthieu M.
1
@KerrekSB:我认为我在链接的问题中已经解决了这个问题。这是允许在一般上下文(不仅仅是在函数参数中)中进行绑定并且不希望它在每次使用时都引起未定义行为的副作用。如果没有生命周期延长:T const& x = f();,然后跟随任何x的odr-use将会是未定义的行为,这就引出了为什么要首先允许它的问题?因此,要么您提供一致性(引用在任何上下文中绑定)与生命周期延长,要么您接受不一致性,而不需要生命周期延长。语言采取了前者。 - David Rodríguez - dribeas
为了简洁明了,我完全搞砸了。请不要将上面的“odr-use”理解为“odr-use”,而是任何访问实际对象(左值-右值转换,访问任何成员等)的使用。 - David Rodríguez - dribeas
嗯,好吧,我还是不太确定(就像我在另一篇帖子中说的那样)—— “完整表达式结束时”规则似乎运行良好,而生命周期扩展同样也可能出人意料(经典的例子是通过另一个返回引用的函数传递引用)。所以我仍然开放接受贡献 :-) - Kerrek SB
@KerrekSB:问题是在函数体内是否允许使用 T const & r = f(); 这样的语句。完整表达式结束点 在函数参数中可以正常工作,但在这种情况下,表达式以分号结束。如果您允许这个表达式而不延长变量 r 的生命周期,那么对 r 的任何使用都将是未定义行为。 - David Rodríguez - dribeas

2

为什么这是可能的?

这里的40是一个字面量。常量引用可以通过字面量和临时变量进行初始化,以延长它们的生命周期。编译器可以通过以下方式实现:

int const& ans = 40;
// transformed:
int __internal_unique_name = 40;
int const& ans = __internal_unique_name;

另一种情况是当您拥有一个函数时,例如:

void f( std::string const& s);

如果您想调用它,可以使用以下方式:

f( "something");

这个临时变量只能绑定到常量引用。


1
这也适用于非const引用... - David Rodríguez - dribeas
@DavidRodríguez-dribeas 不是提供的第二种情况。 - 4pie0
这正是编译器将为您执行的操作,它将调用映射为:std::string __tmp("something"); f(__tmp);。因为你可以这样做并不是允许const引用(独自)的理由,这一事实已被Solaris CC和VS支持将非const引用绑定到临时变量所证明。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas 确认,已编辑 - 4pie0

1

编译器在这方面没有选择。语言规则决定了生命周期。 - Kerrek SB

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