什么是“*this"的右值引用? [英] What is "rvalue reference for *this"?

查看:26
本文介绍了什么是“*this"的右值引用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 clang 的 C++11 状态页面中遇到了一个名为*this 的右值引用"的提议.>

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

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

这个功能是关于什么的?

解决方案

首先,ref-qualifiers for *this"只是一个营销声明".*this 的类型永远不会改变,见本文底部.不过用这种措辞更容易理解.

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

//t.cpp#include 结构测试{void f() &{ std::cout <<左值对象
";}void f() &&{ std::cout <<右值对象
";}};int main(){测试 t;t.f();//左值测试().f();//右值}

输出:

$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp$ ./a.out左值对象右值对象

整个过程都是为了让您可以利用调用函数的对象是右值(例如,未命名的临时对象)这一事实.以下面的代码为例:

struct test2{std::unique_ptr重资源;测试2():heavy_resource(new int[500]) {}运算符 std::unique_ptr() const&{//左值对象,深拷贝std::unique_ptrp(new int[500]);for(int i=0; i <500; ++i)p[i] =heavy_resource[i];返回 p;}运算符 std::unique_ptr() &&{//右值对象//反正我们是垃圾,只是移动资源返回 std::move(heavy_resource);}};

这可能有点做作,但你应该明白.

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


注意:这里后面有很多标准引号和重载解析解释!

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

将调用哪个函数由称为重载解析的过程决定.这个过程相当复杂,所以我们只接触对我们来说重要的一点.

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

§13.3.1 [over.match.funcs]

<块引用>

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

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

struct foo{foo&运算符<<(void*);//实现不重要};foo&运算符<<(foo&, char const*);//实现不重要

您肯定希望以下函数调用 free 函数,不是吗?

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

这就是为什么成员函数和非成员函数都包含在所谓的重载集中的原因.为了使解决方案不那么复杂,标准引用的粗体部分存在.此外,这对我们来说很重要(同一条款):

<块引用>

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

  • cv X 的左值引用",用于在没有引用限定符& 的情况下声明的函数. 引用限定符

  • cv X 的右值引用",用于使用 && ref-qualifier

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

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

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

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

[...]

(最后一点只是意味着您不能基于调用成员函数(或运算符)的对象的隐式转换来欺骗重载解析.)

让我们以本文顶部的第一个示例为例.经过上述转换后,重载集看起来像这样:

void f1(test&);//只匹配左值,链接到 'void test::f() &'void f2(test&&);//将只匹配右值,链接到 'void test::f() &&'

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

//第一次调用 'main' 中的 'f'测试 t;f1(t);//'t' (lvalue) 可以匹配 'test&'(左值参考)//保存在重载集中f2(t);//'t' 不是右值,不能匹配 'test&&'(右值参考)//从重载集合中取出

如果在测试了集合中的所有重载之后,只剩下一个,则重载决议成功,并调用链接到转换后的重载的函数.第二次调用 'f' 也是如此:

//第二次调用 'main' 中的 'f'f1(测试());//'test()' 不是左值,不能匹配 'test&'(左值参考)//从重载集合中取出f2(测试());//'test()' (rvalue) 可以匹配 'test&&'(右值参考)//保存在重载集中

但是请注意,如果我们没有提供任何 ref-qualifier(因此没有重载函数),f1 匹配一个右值(仍然是 §13.3.1):

<块引用>

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

  • 即使隐式对象参数不是const限定的,只要参数在所有其他方面都可以转换为隐式对象参数的类型,右值也可以绑定到参数.

结构测试{void f() { std::cout <<左值或右值对象
";}};int main(){测试 t;t.f();//好的测试().f();//也行}


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

<块引用>

注意这个声明改变了*this的类型.

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

§5.3.1 [expr.unary.op] p1

<块引用>

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

§9.3.2 [class.this] p1

<块引用>

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

Came across a proposal called "rvalue reference for *this" in clang's C++11 status page.

I've read quite a bit about rvalue references and understood them, but I don't think I know about this. I also couldn't find much resources on the web using the terms.

There's a link to the proposal paper on the page: N2439 (Extending move semantics to *this), but I'm also not getting much examples from there.

What is this feature about?

解决方案

First, "ref-qualifiers for *this" is a just a "marketing statement". The type of *this never changes, see the bottom of this post. It's way easier to understand it with this wording though.

Next, the following code chooses the function to be called based on the ref-qualifier of the "implicit object parameter" of the function:

// t.cpp
#include <iostream>

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

int main(){
  test t;
  t.f(); // lvalue
  test().f(); // rvalue
}

Output:

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

The whole thing is done to allow you to take advantage of the fact when the object the function is called on is an rvalue (unnamed temporary, for example). Take the following code as a further example:

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);
  }
};

This may be a bit contrived, but you should get the idea.

Note that you can combine the cv-qualifiers (const and volatile) and ref-qualifiers (& and &&).


Note: Many standard quotes and overload resolution explanation after here!

† To understand how this works, and why @Nicol Bolas' answer is at least partly wrong, we have to dig in the C++ standard for a bit (the part explaining why @Nicol's answer is wrong is at the bottom, if you're only interested in that).

Which function is going to be called is determined by a process called overload resolution. This process is fairly complicated, so we'll only touch the bit that is important to us.

First, it's important to see how overload resolution for member functions works:

§13.3.1 [over.match.funcs]

p2 The set of candidate functions can contain both member and non-member functions to be resolved against the same argument list. So that argument and parameter lists are comparable within this heterogeneous set, a member function is considered to have an extra parameter, called the implicit object parameter, which represents the object for which the member function has been called. [...]

p3 Similarly, when appropriate, the context can construct an argument list that contains an implied object argument to denote the object to be operated on.

Why do we even need to compare member and non-member functions? Operator overloading, that's why. Consider this:

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

foo& operator<<(foo&, char const*); // implementation unimportant

You'd certainly want the following to call the free function, don't you?

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

That's why member and non-member functions are included in the so-called overload-set. To make the resolution less complicated, the bold part of the standard quote exists. Additionally, this is the important bit for us (same clause):

p4 For non-static member functions, the type of the implicit object parameter is

  • "lvalue reference to cv X" for functions declared without a ref-qualifier or with the & ref-qualifier

  • "rvalue reference to cv X" for functions declared with the && ref-qualifier

where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration. [...]

p5 During overload resolution [...] [t]he implicit object parameter [...] retains its identity since conversions on the corresponding argument shall obey these additional rules:

  • no temporary object can be introduced to hold the argument for the implicit object parameter; and

  • no user-defined conversions can be applied to achieve a type match with it

[...]

(The last bit just means that you can't cheat overload resolution based on implicit conversions of the object a member function (or operator) is called on.)

Let's take the first example at the top of this post. After the aforementioned transformation, the overload-set looks something like this:

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

Then the argument list, containing an implied object argument, is matched against the parameter-list of every function contained in the overload-set. In our case, the argument list will only contain that object argument. Let's see how that looks like:

// 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

If, after all overloads in the set are tested, only one remains, the overload resolution succeeded and the function linked to that transformed overload is called. The same goes for the second call to '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

Note however that, had we not provided any ref-qualifier (and as such not overloaded the function), that f1 would match an rvalue (still §13.3.1):

p5 [...] For non-static member functions declared without a ref-qualifier, an additional rule applies:

  • even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.

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

int main(){
  test t;
  t.f(); // OK
  test().f(); // OK too
}


Now, onto why @Nicol's answer is atleast partly wrong. He says:

Note that this declaration changes the type of *this.

That is wrong, *this is always an lvalue:

§5.3.1 [expr.unary.op] p1

The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.

§9.3.2 [class.this] p1

In the body of a non-static (9.3) member function, the keyword this is a prvalue expression whose value is the address of the object for which the function is called. The type of this in a member function of a class X is X*. [...]

这篇关于什么是“*this"的右值引用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆