类,右值和右值引用

14

左值是绑定到内存的明确区域的值,而右值是表达式值,其存在是临时的,并不一定引用到明确的内存区域。每当在期望右值的位置使用左值时,编译器执行左值到右值的转换,然后继续评估。

http://www.eetimes.com/discussion/programming-pointers/4023341/Lvalues-and-Rvalues

无论何时我们构建一个临时(匿名)类对象或从函数返回一个临时类对象,尽管该对象是临时的,但它是可寻址的。但是,该对象仍然是有效的右值。这意味着对象是a) 可寻址的右值或b) 当编译器期望使用左值时,从左值隐式转换为右值。
例如:
class A
{
public:
    int x;
    A(int a) { x = a; std::cout << "int conversion ctor\n"; }
    A(A&) { std::cout << "lvalue copy ctor\n"; }
    A(A&&) { std::cout << "rvalue copy ctor\n"; }
};
A ret_a(A a) 
{
    return a;
}

int main(void)
{
    &A(5); // A(5) is an addressable object
    A&& rvalue = A(5); // A(5) is also an rvalue
}

我们也知道,函数返回的临时对象(在下面的代码段中为a)是左值,如下所示:
int main(void)
{
    ret_a(A(5));
}

产生以下输出:
int转换构造函数
左值复制构造函数
表明使用实际参数A(5)调用函数ret_a时,调用了转换构造函数A::A(int),该构造函数使用值5构造函数的形式参数a。
当函数执行完成后,它将使用a作为其参数构造一个临时的A对象,这将调用A::A(A&)。但是,如果我们从重载构造函数的列表中删除A::A(A&),返回的临时对象仍将匹配rvalue-reference构造函数A::A(A&&)。
这是我不太理解的地方:对象a如何既匹配rvalue引用又匹配lvalue引用?显然,A::A(A&)比A::A(A&&)更匹配(因此a必须是一个lvalue)。但是,由于rvalue引用不能被初始化为lvalue,而形式参数a是lvalue,所以它不应该能够匹配到对A::A(A&&)的调用。如果编译器进行lvalue-to-rvalue转换,那就很容易了。事实上,从'A'到'A&'的转换也很简单,两个函数应该具有相同的隐式转换序列等级,因此当A::A(A&)和A::A(A&&)都在重载函数候选集中时,编译器不应该能够推断出最佳匹配函数。
此外,之前我问过的问题是:
给定的对象如何既匹配rvalue引用又匹配lvalue引用?

5
“lvalue”和“rvalue”指的是表达式,而不是对象。 - Cheers and hth. - Alf
谢谢。那么问题是如何将a对象绑定到rvalue和lvalue引用?此外:“int n = 3; n是指向int对象的表达式。表达式n是lvalue。”。http://www.eetimes.com/discussion/programming-pointers/4023341/Lvalues-and-Rvalues 如果这是正确的,那么在初始化时a对象不就被认为是一个表达式吗? - No One
1个回答

6

对于我来说:

int main(void)
{
    ret_a(A(5));
}

产出:

int conversion ctor
rvalue copy ctor

函数执行完成后,使用 a 作为其参数构建临时对象 A,从而调用A::A(A&)

实际上并非如此。函数ret_a完成执行后,使用 a 作为其参数构建临时对象 A,从而调用A:A(A&&)。这是由于[class.copy]/p331所述:

当满足复制操作的省略条件或即将满足该条件时(除了源对象是函数参数之外),并且要复制的对象被一个左值引用指定,则首先按照对象被右值引用指定的方式执行选择复制构造函数的重载解析。 如果重载解析失败,或者所选构造函数的第一个参数的类型不是对象类型的右值引用(可能带有cv限定符),则再次执行重载解析,将对象视为左值。【注:无论省略是否发生,都必须执行这两个阶段的重载解析。它确定要调用的构造函数,如果未发生节约,则所选构造函数必须是可访问的。 - 结束备注】

但是,如果您删除A::A(A&&)构造函数,则将选择A::A(&)进行返回。虽然在这种情况下,然后会因为不能使用一个右值来构建参数a而使其构建失败。但是暂且不管那个,我相信你最终的问题是:

如何使给定对象同时匹配右值引用和左值引用?

这句话指的是:
return a;

答案在上面引用的草案标准中:首先尝试作为rvalue对待a进行重载解析。如果失败,则再次使用a作为lvalue进行重载解析。仅在允许省略拷贝(例如返回语句)的情况下尝试这个两阶段过程。
C++0x草案最近已更改,允许在返回按值传递的参数时使用两级重载解析过程(如您的示例中)。这就是我们看到不同编译器变化行为的原因。

@Howard 非常感谢您的见解。但是,我的原始问题中没有任何打字错误。调用了 A::A(A&)。您使用哪个编译器?我正在使用 MSVC++。 - No One
我不熟悉MSVC++,但我记得他们有一个古老的兼容性错误,允许rvalue绑定到lvalue引用(当它们仍被称为引用时)。我记得有一种方法可以关闭这种行为,但我不记得语法。你能找到那个-Z标志并尝试一下吗? - Howard Hinnant
就此而言,我刚使用MSVC 2010和GCC 4.5.2编译了该代码。两次都得到了与No One相同的输出:int conversion ctorlvalue copy ctor - dappawit
2
@大家:我在称这是“相当古老的规则”时犯了一个错误。实际上,这种行为最近才被标准强制执行。它是cwg 1148。我忽略了返回语句正在返回一个参数。是的,正确答案是“rvalue复制构造函数”。然而,直到2010年11月,正确答案仍然是“lvalue复制构造函数”。对于所有的混淆,我感到抱歉。 - Howard Hinnant
@Howard,在Cwg 1148之前,函数参数是符合复制省略的条件的,因此,“当满足复制操作的省略标准时”需要重载解析首先将a视为rvalue。Cwg 1148使函数参数不再适用于复制省略,因此需要修改第35段,该段在当时完全依赖于复制省略的标准。我有什么遗漏吗? - Johannes Schaub - litb
显示剩余8条评论

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