c++ - 什么是 "rvalue reference for *this"?

标签 c++ c++11 move-semantics c++-faq qualifiers

在 clang 的 C++11 status page 中遇到了一个名为“*this 的右值引用”的提议.

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

页面上有指向提案文件的链接:N2439 (将 move 语义扩展到 *this),但我也没有从那里得到太多例子。

这个功能是关于什么的?

最佳答案

首先,“*this 的 ref-qualifiers”只是一个“营销声明”。 *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
}

输出:

$ 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 限定符(constvolatile)和ref 限定符( &&&)。


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

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

要调用哪个函数由称为重载解析的过程决定。这个过程相当复杂,所以我们只触及对我们重要的部分。

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

§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.

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

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

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

您肯定希望下面的代码调用 free 函数,不是吗?

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

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

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

[...]

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

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

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

如果在测试集中的所有重载之后,只剩下一个,则重载解析成功,并调用链接到该转换后的重载的函数。第二次调用“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-qualifier(因此没有重载函数),f1 匹配一个右值(仍然是 §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
}

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

Note that this declaration changes the type of *this.

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

§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*. [...]

关于c++ - 什么是 "rvalue reference for *this"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35142115/

相关文章:

c++ - c++11对于三种方法的规则如何实现 "... = default;"

c++ - 执行默认: in switch-case selection statements in C++

c++ - 动态分配函数指针数组时发出警告

c++ - std::string 上的段错误

c++ - 在继承层次结构中移动构造函数

c++ - 在 move 构造函数中窃取

C++ 无法选择正确的部分模板特化

c++ - 返回一个右值——这段代码有什么问题?

c++ - 为什么使用 `none const copy constructor` 时编译器会报错?

c++ - 为什么 Visual Studio 在这种情况下不执行返回值优化 (RVO)