什么是“rvalue reference for * this”? [英] What is "rvalue reference for *this"?

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

问题描述

在clang的 C ++ 11状态页中,名为rvalue reference for * this的提案。 p>

我已经阅读了很多关于右值引用和理解他们,但我不认为我知道这一点。我也无法在网络上使用这些条款找到很多资源。



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



这个功能是什么?

解决方案

首先,* this的ref-qualifiers只是一个营销声明。 * this 的类型永远不会改变,请参阅此帖的底部。



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

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

输出:


$ b b

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

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

  struct test2 {
std :: unique_ptr< int []&重要资源

test2()
:heavy_resource(new int [500]){}

operator std :: unique_ptr< int []&
// 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;
}

操作符std :: unique_ptr< int []>()&&$ {
// rvalue object
//我们是垃圾,只是移动资源
return std :: move(heavy_resource);
}
};

这可能有点麻烦,但你应该得到这个想法。



请注意,您可以将 cv-qualifiers const volatile )和 ref-qualifiers & && )。






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



†要了解这是如何工作的,以及为什么@Nicol Bolas的答案至少部分错误,我们必须挖掘C ++标准一点(解释为什么@ Nicol的



要调用的函数是由一个称为 overload的进程决定的分辨率。这个过程是相当复杂的,所以我们只会碰到对我们很重要的位。



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

§13.3.1[over.match.funcs]


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



p3类似地,上下文可以构造一个参数列表,其中包含一个


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

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

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

你肯定希望下面的函数调用free函数,不要?

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

这就是为什么成员函数和非成员函数都包含在所谓的重载集中。为了使分辨率不那么复杂,标准报价的粗体部分存在。另外,这是我们的重要的一点(同一条款):


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




  • 引用cv X 用于未使用 ref-qualifier 声明的函数或& ref-qualifier

    >
  • $ c>&&&< / code>



$ b >其中 X 是函数是其成员的类,而 cv 是成员函数声明的cv-qualification。 [...]



p5在重载解析[...] [t]期间,隐式对象参数[...]必须遵守以下附加规则:




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




  • [...]


  • (最后一位只是意味着你不能基于对象的隐式转换的欺骗重载解析,成员函数(或操作符)被调用。)



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

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

    然后,包含隐含对象参数的参数列表与包含在重载集中的每个函数的参数列表进行匹配。在我们的例子中,参数列表将仅包含该对象参数。让我们看看如下:

      //首先调用'main'中的'f'
    test t;
    f1(t); //'t'(lvalue)可以匹配'test&'(lvalue reference)
    //保持在overload-set中
    f2(t); //'t'not a rvalue,could not match'test&&'(rvalue reference)
    //从超载集合中取出

    如果在集合中的所有重载都被测试之后,只剩下一个,重载解析成功,并且调用链接到转换的重载的函数。第二次调用'f'也是如此:

      //在'main'中第二次调用'f'
    f1(test()); // test()'不是一个左值,不能匹配'test&'(左值引用)
    //从超载集合中取出
    f2(test()); //'test()'(rvalue)可以匹配'test&'(rvalue reference)
    //保持在重载集合

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


    p5 [...]对于未使用 ref-qualifier 声明的非静态成员函数,应用另一个规则:




    • 即使隐式对象参数不是 const -qualified,只要所有其他方面的参数可以转换为隐式对象参数的类型。




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

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






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


    请注意,此声明更改了 * this 的类型。 / p>

    这是错误的, * this 总是 小价:



    §5.3.1[expr.unary.op] p1


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


    < blockquote>

    §9.3.2[class.this] p1



    < >

    在非静态(9.3)成员函数的正文中,关键字 this 是一个prvalue表达式,其值是对象的地址为其调用该函数。类 X 的成员函数中的类型 X c $ c>。 [...]



    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\n"; }
      void f() &&{ std::cout << "rvalue object\n"; }
    };
    
    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!\n";
    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\n"; }
    };
    
    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*. [...]

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

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