什么是“右值参考”?

在clang的C ++ 11状态页面中find了一个叫做“* this的右值引用”的提议。

我读了很多关于右值的引用并理解了它们,但我不认为我知道这个。 我也无法使用这些条款在网上find很多资源。

有一个链接到页面上的提案文件: N2439 (将移动语义扩展到*这个),但我也没有从那里得到很多例子。

这个function是关于什么的?

首先,“这个”ref-qualifiers“只是一个”营销声明“。 *thistypes永远不会改变,看到这篇文章的底部。 尽pipe用这种措辞来理解它更容易一些。

接下来,下面的代码根据函数的“隐式对象参数”的ref-qualifierselect要调用的函数:

 // t.cpp #include <iostream> struct test{ void f() &{ std::cout << "lvalue object\n"; } void f() &&{ std::cout << "rvalue object\n"; } }; int main(){ test t; tf(); // lvalue test().f(); // rvalue } 

输出:

 $ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp $ ./a.out lvalue object rvalue object 

整个事情是为了让你利用这个事实,当函数被调用的对象是右值(例如,未命名的临时值)。 以下面的代码作为进一步的例子:

 struct test2{ std::unique_ptr<int[]> heavy_resource; test2() : heavy_resource(new int[500]) {} operator std::unique_ptr<int[]>() const&{ // lvalue object, deep copy std::unique_ptr<int[]> p(new int[500]); for(int i=0; i < 500; ++i) p[i] = heavy_resource[i]; return p; } operator std::unique_ptr<int[]>() &&{ // rvalue object // we are garbage anyways, just move resource return std::move(heavy_resource); } }; 

这可能有些做作,但你应该明白。

请注意,您可以将cv-qualifiersconstvolatile )和ref-qualifiers&&& )组合在一起。


注意:这里有很多标准的报价和重载parsing的解释!

†要理解这是如何工作的,以及为什么@Nicol Bolas的答案至less在一定程度上是错误的,我们必须深入挖掘C ++标准(解释为什么@ Nicol的答案是错误的部分在底部,如果你只对此感兴趣)。

将要调用哪个函数是由称为重载分辨率的过程确定的。 这个过程相当复杂,所以我们只会触及对我们很重要的一点。

首先,看看成员函数的重载分辨率是如何工作的:

§13.3.1 [over.match.funcs]

p2候选函数集可以同时包含成员函数和非成员函数,并根据相同的参数列表进行parsing。 所以这个参数和参数列表在这个异构集合中是可比较的, 一个成员函数被认为有一个额外的参数,叫做隐式对象参数,它代表了成员函数被调用的对象 。 […]

p3类似地,在适当的情况下,上下文可以构造一个参数列表,其中包含一个隐含的对象参数来表示要操作的对象。

为什么我们甚至需要比较成员和非成员函数? 运算符重载,这就是为什么。 考虑一下:

 struct foo{ foo& operator<<(void*); // implementation unimportant }; foo& operator<<(foo&, char const*); // implementation unimportant 

你一定会想要以下的免费function,不是吗?

 char const* s = "free foo!\n"; foo f; f << s; 

这就是为什么成员和非成员函数被包含在所谓的重载集合中。 为了使分辨率不复杂,标准报价的大胆部分存在。 另外,这对于我们来说是重要的(同一条款):

p4对于非静态成员函数,隐式对象参数的types为

  • 对于不带ref-qualifier或带有& ref-qualifier声明的函数,“左值引用cv X

  • && ref-qualifier声明的函数的“右值引用cv X

其中X是函数所属的类, cv是成员函数声明中的cv限定。 […]

p5在重载parsing[…] [t]中,隐式对象参数保留其身份,因为相应参数的转换必须遵守以下附加规则:

  • 不能引入临时对象来保存隐式对象参数的参数; 和

  • 没有用户定义的转换可以被应用来实现与它的types匹配

[…]

(最后一点意味着你不能根据成员函数(或操作符)被调用的对象的隐式转换来欺骗重载parsing。)

我们来看这个post顶部的第一个例子。 在上述转换之后,重载集合看起来像这样:

 void f1(test&); // will only match lvalues, linked to 'void test::f() &' void f2(test&&); // will only match rvalues, linked to 'void test::f() &&' 

然后将包含隐含对象参数的参数列表与包含在过载集合中的每个函数的参数列表进行匹配。 在我们的例子中,参数列表将只包含那个对象参数。 让我们看看如何:

 // first call to 'f' in 'main' test t; f1(t); // 't' (lvalue) can match 'test&' (lvalue reference) // kept in overload-set f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference) // taken out of overload-set 

如果在testing集合中的所有重载之后,只剩下一个,则重载parsing成功,并且调用与转换的超载关联的函数。 第二次调用'f'也是一样的:

 // second call to 'f' in 'main' f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference) // taken out of overload-set f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference) // kept in overload-set 

但是请注意,如果我们没有提供任何ref限定符 (因此不会重载函数),则f1 与右值相匹配(仍然是§13.3.1 ):

p5 […]对于没有ref-qualifier声明的非静态成员函数,附加规则适用:

  • 即使隐式对象参数不是const限定的,只要参数可以转换为隐式对象参数的types,就可以将右值绑定到参数。
 struct test{ void f() { std::cout << "lvalue or rvalue object\n"; } }; int main(){ test t; tf(); // OK test().f(); // OK too } 

现在,为什么@尼科尔的答案是至less部分错误的。 他说:

注意这个声明改变了*this的types。

这是错误的, *this 总是一个左值:

§5.3.1 [expr.unary.op] p1

一元运算符执行间接寻址 :应用它的expression式应该是一个指向对象types的指针或一个指向函数types的指针, 结果是指向expression式指向的对象或函数的左值

§9.3.2 [class.this] p1

在非静态(9.3)成员函数的主体中,关键字this是一个prvalueexpression式,其值是调用该函数的对象的地址。 this在类X的成员函数中的types是X* 。 […]

左值ref-qualifier表单有一个额外的用例。 C ++ 98的语言允许非常量成员函数被称为右值的类实例。 这导致了各种各样的与真正的价值概念背道而驰的古怪,并且偏离了内buildtypes的工作方式:

 struct S { S& operator ++(); S* operator &(); }; S() = S(); // rvalue as a left-hand-side of assignment! S& foo = ++S(); // oops, dangling reference &S(); // taking address of rvalue... 

左值ref-qualifiers解决了这些问题:

 struct S { S& operator ++() &; S* operator &() &; const S& operator =(const S&) &; }; 

现在,操作员像内置types一样工作,只接受左值。

假设你在一个类上有两个函数,都有相同的名字和签名。 但是其中一个被声明为const

 void SomeFunc() const; void SomeFunc(); 

如果一个类实例不是const ,重载parsing将优先select非const的版本。 如果实例是const ,则用户只能调用const版本。 而this指针是一个const指针,所以实例是不能改变的。

什么“r值参考这个”可以让你添加另一个select:

 void RValueFunc() &&; 

这允许你有一个函数, 只有当用户通过一个适当的r值调用它时才能被调用。 所以,如果这是在Objecttypes:

 Object foo; foo.RValueFunc(); //error: no `RValueFunc` version exists that takes `this` as l-value. Object().RValueFunc(); //calls the non-const, && version. 

这样,可以根据是否通过r值访问对象来专门化行为。

请注意,您不允许在r值参考版本和非参考版本之间过载。 也就是说,如果你有一个成员函数的名字,它的所有版本在this上使用l / r值限定符,或者没有一个使用。 你不能这样做:

 void SomeFunc(); void SomeFunc() &&; 

你必须这样做:

 void SomeFunc() &; void SomeFunc() &&; 

注意这个声明改变了*this的types。 这意味着&&版本的所有访问成员都是r值引用。 所以可以轻松地从物体内移动。 在提案的第一个版本中给出的例子是(注意:C ++ 11的最终版本可能不是正确的,从最初的“r-value from this”提案开始):

 class X { std::vector<char> data_; public: // ... std::vector<char> const & data() const & { return data_; } std::vector<char> && data() && { return data_; } }; X f(); // ... X x; std::vector<char> a = x.data(); // copy std::vector<char> b = f().data(); // move 
Interesting Posts