C++ 什么是“*this 的右值引用”?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8610571/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is "rvalue reference for *this"?
提问by ryaner
Came across a proposal called "rvalue reference for *this" in clang's C++11 status page.
在 clang 的C++11 状态页面中遇到了一个名为“*this 的右值引用”的提议。
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.
页面上有一个提案文件的链接:N2439(将移动语义扩展到 *this),但我也没有从那里得到太多示例。
What is this feature about?
这个功能是关于什么的?
回答by Xeo
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.
首先,“*this 的引用限定符”只是一个“营销声明”。*this
永不改变的类型,见这篇文章的底部。不过,用这种措辞更容易理解它。
Next, the following code chooses the function to be called based on the ref-qualifierof the "implicit object parameter" of the function?:
接下来,以下代码根据函数的“隐式对象参数”的ref 限定符选择要调用的函数?:
// 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 &&
).
请注意,您可以组合使用cv 限定符(const
和volatile
)和引用限定符(&
和&&
)。
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).
? 要了解这是如何工作的,以及为什么@Nicol Bolas 的答案至少有一部分是错误的,我们必须深入研究一下 C++ 标准(解释为什么@Nicol 的答案错误的部分在底部,如果您只是对此感兴趣)。
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]
§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 argumentto denote the object to be operated on.
p2 候选函数集可以包含要针对相同参数列表解析的成员函数和非成员函数。为了使参数和参数列表在这个异构集合中具有可比性,成员函数被认为具有一个额外的参数,称为隐式对象参数,它表示已为其调用成员函数的对象。[...]
p3 同样,在适当的时候,上下文可以构造一个参数列表,其中包含一个隐含的对象参数来表示要对其进行操作的对象。
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?
您肯定希望以下调用 free 函数,不是吗?
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-qualifieror with the&
ref-qualifier“rvalue reference to cv
X
” for functions declared with the&&
ref-qualifierwhere
X
is the class of which the function is a member and cvis 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
[...]
p4 对于非静态成员函数,隐式对象参数的类型为
“对cv 的左值引用
X
”,用于未使用ref 限定符或带有ref 限定符声明的函数&
使用ref 限定符声明的函数的“对cv 的右值引用
X
”&&
其中
X
是函数所属的类,cv是成员函数声明中的 cv 限定。[...]p5 在重载决议 [...] [t] 隐式对象参数 [...] 保留其身份,因为对相应参数的转换应遵守以下附加规则:
不能引入临时对象来保存隐式对象参数的参数;和
不能应用用户定义的转换来实现与它的类型匹配
[...]
(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':
如果在测试集合中的所有重载之后,只剩下一个,则重载决议成功,并调用链接到该转换的重载的函数。第二次调用 '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
wouldmatch an rvalue (still §13.3.1
):
但是请注意,如果我们没有提供任何ref 限定符(因此没有重载函数),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.
p5 [...] 对于没有ref-qualifier声明的非静态成员函数,附加规则适用:
- 即使隐式对象参数没有
const
限定,只要在所有其他方面参数可以转换为隐式对象参数的类型,右值也可以绑定到参数。
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:
现在,为什么@Nicol 的回答至少是部分错误的。他说:
Note that this declaration changes the type of
*this
.
请注意,此声明更改了
*this
.
That is wrong, *this
is alwaysan lvalue:
这是不对的,*this
是始终左值:
§5.3.1 [expr.unary.op] p1
§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 lvaluereferring to the object or function to which the expression points.
一元运算
*
符执行间接操作:应用它的表达式应是指向对象类型的指针,或指向函数类型的指针,结果是引用表达式指向的对象或函数的左值。
§9.3.2 [class.this] p1
§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 ofthis
in a member function of a classX
isX*
. [...]
在非静态 (9.3) 成员函数的主体中,关键字
this
是一个纯右值表达式,其值是调用该函数的对象的地址。类的this
成员函数中的类型X
是X*
。[...]
回答by JohannesD
There is an additional use case for the lvalue ref-qualifier form. C++98 has language that allows non-const
member functions to be called for class instances that are rvalues. This leads to all kinds of weirdness that is against the very concept of rvalueness and deviates from how built-in types work:
左值引用限定符形式还有一个额外的用例。C++98 的语言允许const
为右值的类实例调用非成员函数。这导致了各种与右值概念背道而驰的奇怪之处,并且偏离了内置类型的工作方式:
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...
Lvalue ref-qualifiers solve these problems:
左值引用限定符解决了这些问题:
struct S {
S& operator ++() &;
S* operator &() &;
const S& operator =(const S&) &;
};
Now the operators work like those of the builtin types, accepting only lvalues.
现在运算符的工作方式类似于内置类型,只接受左值。
回答by Nicol Bolas
Let's say you have two functions on a class, both with the same name and signature. But one of them is declared const
:
假设您在一个类上有两个函数,它们都具有相同的名称和签名。但其中之一被声明为const
:
void SomeFunc() const;
void SomeFunc();
If a class instance is not const
, overload resolution will preferentially select the non-const version. If the instance is const
, the user can only call the const
version. And the this
pointer is a const
pointer, so the instance cannot be changed.
如果类实例不是const
,重载决议将优先选择非常量版本。如果实例为const
,则用户只能调用const
版本。并且this
指针是const
指针,所以实例不能改变。
What "r-value reference for this` does is allow you to add another alternative:
“this` 的 r 值引用的作用是允许您添加另一个替代方案:
void RValueFunc() &&;
This allows you to have a function that can onlybe called if the user calls it through a proper r-value. So if this is in the type Object
:
这允许您拥有一个只能在用户通过正确的 r 值调用它时才能调用的函数。所以如果这是在类型中Object
:
Object foo;
foo.RValueFunc(); //error: no `RValueFunc` version exists that takes `this` as l-value.
Object().RValueFunc(); //calls the non-const, && version.
This way, you can specialize behavior based on whether the object is being accessed via an r-value or not.
这样,您可以根据对象是否通过 r 值访问来专门化行为。
Note that you are not allowed to overload between the r-value reference versions and the non-reference versions. That is, if you have a member function name, all of its versions either use the l/r-value qualifiers on this
, or none of them do. You can't do this:
请注意,不允许在 r 值参考版本和非参考版本之间进行重载。也就是说,如果你有一个成员函数名,它的所有版本要么使用 l/r 值限定符 on this
,要么都不使用。你不能这样做:
void SomeFunc();
void SomeFunc() &&;
You must do this:
你必须这样做:
void SomeFunc() &;
void SomeFunc() &&;
Note that this declaration changes the type of *this
. This means that the &&
versions all access members as r-value references. So it becomes possible to easily move from within the object. The example given in the first version of the proposal is (note: the following may not be correct with the final version of C++11; it's straight from the initial "r-value from this" proposal):
请注意,此声明更改了*this
. 这意味着&&
版本所有访问成员作为 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