什么是"这个的右值参考"?

rya*_*ner 229 c++ qualifiers c++-faq move-semantics c++11

在clang的C++ 11状态页面中遇到了一个名为"rvalue reference for*this"的提案.

我已经阅读了很多关于rvalue引用并理解它们的内容,但我认为我不知道这一点.我也无法使用这些条款在网上找到太多资源.

页面上的提案文件有一个链接:N2439(将移动语义扩展到*this),但我也没有从中获得太多的例子.

这个功能是什么?

Xeo*_*Xeo 284

首先,"*这个参考资格赛"只是一个"营销声明".*this永不改变的类型,请参阅本文的底部.用这个措辞来理解它更容易.

接下来,以下代码根据函数的"隐式对象参数" 的ref-qualifier选择要调用的函数:

// t.cpp
#include <iostream>

struct test{
  void f() &{ std::cout << "lvalue object\n"; }
  void f() &&{ std::cout << "rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // lvalue
  test().f(); // rvalue
}
Run Code Online (Sandbox Code Playgroud)

输出:

$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object
Run Code Online (Sandbox Code Playgroud)

完成所有操作是为了让您在调用函数的对象是rvalue(例如,未命名的临时对象)时利用这一事实.以下代码为例:

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);
  }
};
Run Code Online (Sandbox Code Playgroud)

这可能有点人为,但你应该明白这个想法.

请注意,您可以组合使用cv-qualifiers(constvolatile)和ref-qualifiers(&&&).


注意:这里有很多标准报价和重载解析说明!

†为了理解这是如何工作的,以及为什么@Nicol Bolas的答案至少部分是错误的,我们必须深入研究C++标准(解释为什么@Nicol的答案是错误的部分在底部,如果你是只对此感兴趣).

将要调用哪个函数由称为重载解析的过程确定.这个过程相当复杂,所以我们只会触及对我们很重要的位.

首先,重要的是要了解成员函数的重载解析是如何工作的:

§13.3.1 [over.match.funcs]

p2候选函数集可以包含要针对同一参数列表解析的成员函数和非成员函数.因此,参数和参数列表在此异构集中是可比较的,成员函数被认为具有额外的参数,称为隐式对象参数,其表示已为其调用成员函数的对象.[...]

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

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

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

foo& operator<<(foo&, char const*); // implementation unimportant
Run Code Online (Sandbox Code Playgroud)

您当然希望以下内容可以调用免费功能,不是吗?

char const* s = "free foo!\n";
foo f;
f << s;
Run Code Online (Sandbox Code Playgroud)

这就是成员和非成员函数包含在所谓的重载集中的原因.为了使分辨率更简单,标准报价的粗体部分存在.另外,这对我们来说是重要的一点(同样的条款):

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

  • "左值参考CV X "为没有声明的函数REF-限定符或与& REF-限定符

  • 对于使用ref-qualifier声明的函数的"rvalue对cv的 引用X"&&

where X是函数所属的类,cv是成员函数声明的cv-qualification.[...]

p5在重载解析[...] [t]期间,隐式对象参数[...]保留其标识,因为对相应参数的转换应遵守这些附加规则:

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

  • 不能应用用户定义的转换来实现与它的类型匹配

[...]

(最后一位只是意味着你不能根据调用成员函数(或操作符)的对象的隐式转换来欺骗重载决策.)

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

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

然后,包含隐含对象参数的参数列表与重载集中包含的每个函数的参数列表进行匹配.在我们的例子中,参数列表将只包含该对象参数.让我们看看它是怎样的:

// 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
Run Code Online (Sandbox Code Playgroud)

如果在测试集合中的所有重载之后,只剩下一个,则重载解析成功,并且调用链接到该转换的重载的函数.第二次调用'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
Run Code Online (Sandbox Code Playgroud)

但请注意,如果我们没有提供任何ref-qualifier(并且因此没有重载函数),那f1 匹配rvalue(仍然§13.3.1):

p5 [...]对于在没有ref-qualifier的情况下声明的非静态成员函数,还适用另一条规则:

  • 即使隐式对象参数不是const-qualified,也可以将rvalue绑定到参数,只要在所有其他方面,参数可以转换为隐式对象参数的类型.
struct test{
  void f() { std::cout << "lvalue or rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // OK
  test().f(); // OK too
}
Run Code Online (Sandbox Code Playgroud)

现在,为什么@ Nicol的答案至少部分是错误的.他说:

请注意,此声明更改了类型*this.

这是不对的,*this始终左值:

§5.3.1 [expr.unary.op] p1

一元运算*符执行间接:它所应用的表达式应该是指向对象类型的指针,或指向函数类型的指针,结果是引用表达式指向的对象或函数的左值.

§9.3.2 [class.this] p1

在非静态(9.3)成员函数的主体中,关键字this是一个prvalue表达式,其值是调用该函数的对象的地址.类的this成员函数的类型XX*.[...]

  • “ *的类型永远不会改变”您可能应该更清楚一点,它不会根据R / L值限定而改变。但是它可以在const / non-const之间改变。 (2认同)

Joh*_*esD 77

左值ref-qualifier表单还有一个额外的用例.C++ 98具有允许const为作为rvalues的类实例调用非成员函数的语言.这会产生各种奇怪的现象,这种怪异与rvalueness的概念相悖,并且与内置类型的工作方式不同:

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...
Run Code Online (Sandbox Code Playgroud)

Lvalue ref-qualifiers解决了这些问题:

struct S {
  S& operator ++() &;
  S* operator &() &;
  const S& operator =(const S&) &;
};
Run Code Online (Sandbox Code Playgroud)

现在运算符的工作方式类似于内置类型,只接受左值.


Nic*_*las 28

假设你在一个类上有两个函数,它们都具有相同的名称和签名.但其中一个被宣布const:

void SomeFunc() const;
void SomeFunc();
Run Code Online (Sandbox Code Playgroud)

如果类实例不是const,则重载解析将优先选择非const版本.如果是实例const,则用户只能调用该const版本.而this指针是一个const指针,以便实例不能被改变.

什么"此值的r值参考"允许您添加另一个替代方案:

void RValueFunc() &&;
Run Code Online (Sandbox Code Playgroud)

这允许您拥有一个只有在用户通过正确的r值调用它时才能调用的函数.所以,如果这是类型Object:

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

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

请注意,不允许在r值引用版本和非引用版本之间重载.也就是说,如果你有一个成员函数名,那么它的所有版本都使用l/r-value限定符this,或者都没有.你不能这样做:

void SomeFunc();
void SomeFunc() &&;
Run Code Online (Sandbox Code Playgroud)

你必须这样做:

void SomeFunc() &;
void SomeFunc() &&;
Run Code Online (Sandbox Code Playgroud)

请注意,此声明更改了类型*this.这意味着&&版本都将访问成员作为r值引用.因此可以轻松地从对象内移动.该提案的第一个版本中给出的示例是(注意:以下可能与C++ 11的最终版本不一致;它直接来自最初的"来自此"提议的r值):

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
Run Code Online (Sandbox Code Playgroud)

  • 我想我可以解释一下,毕竟我为C++ 11创建了这个功能;)Xeo坚持认为它不会改变`*this`的类型,但是我可以理解混淆来自何处.这是因为ref-qualifier更改了隐式(或"隐藏")函数参数的类型,在此过载解析和函数调用期间,"this"(在此处引用!!)对象被绑定到该参数.所以,没有改变`*this`,因为这是固定的,因为Xeo解释.而是改变"hiddden"参数使其成为左值或右值引用,就像`const`函数限定符一样使它成为`const`等. (13认同)
  • 对,我有点想到了我的第二个问题的原因.我不知道,对临时成员的rvalue引用是否会延长该临时成员或其成员的生命周期?我可以发誓我前段时间在SO上看到了一个问题...... (3认同)
  • 我想你需要`std :: move`第二个版本,非?另外,为什么rvalue引用返回? (2认同)