首先,“*this 的引用限定符”只是一个“营销声明”。 *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-qualifiers(const 和 volatile)和 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
您肯定希望以下调用免费函数,不是吗?
char const* s = "free foo!\n";
foo f;
f << s;
这就是为什么成员函数和非成员函数都包含在所谓的重载集中的原因。为了使解决方案不那么复杂,标准引用的粗体部分存在。此外,这对我们来说很重要(同一个子句):
p4 对于非静态成员函数,隐含对象参数的类型为
其中X 是函数所属的类,cv 是成员函数声明上的 cv 限定。 [...]
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() &&'
然后,包含隐含对象参数的参数列表与重载集中包含的每个函数的参数列表进行匹配。在我们的例子中,参数列表将只包含该对象参数。让我们看看它是什么样子的:
// 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 将匹配一个右值(仍然是@ 987654341@):
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
}
现在,为什么@Nicol 的回答至少部分错误。他说:
请注意,此声明会更改 *this 的类型。
这是错误的,*this总是是一个左值:
§5.3.1 [expr.unary.op] p1
一元* 运算符执行间接:应用它的表达式应该是指向对象类型的指针,或指向函数类型的指针,结果是左值指的是表达式所指向的对象或函数。
§9.3.2 [class.this] p1
在非静态 (9.3) 成员函数的主体中,关键字this 是一个prvalue 表达式,其值是调用该函数的对象的地址。类X 的成员函数中this 的类型是X*。 [...]