想了解标题的内容,我们可以先简单了解下c++中的左值和右值。
左值和右值
- 左值:能对表达式取地址,就是左值
- 右值:不能对表达式取地址,就是右值
- 将亡值:和右值引用相关的表达式,这样的表达式通常是将要移动的对象、T&&函数返回值、std::move()函数的返回值等
- 纯右值:就是c++98标准右值的概念
这里可以把将亡值和纯右值都看作右值即可。
左值引用和右值引用
- 左值引用
如下:
int a = 1;
int& ref = a; //ref 就是a的别名,a是左值,ref就是左值引用
- 右值引用
右值引用的符号是&&,如下:
int&& a = 1; //这里1是右值,所以a就是右值引用
这里需要注意的是:左值引用只能绑定左值,右值引用只能绑定右值。但是常量左值const T&,既可以绑定左值又可以绑定右值。
移动构造和移动赋值
举个简单例子:
class MyString{
public:
MyString(): data_(nullptr), length_(0){
}
~MyString(){
if (nullptr != data_) {
delete[] data_;
data_ = nullptr;
}
}
// 移动构造
MyString(MyString&& str) {
data_ = str.data_;
length_ = str.length_;
str.data_ = nullptr;
str.length_ = 0;
}
// 移动赋值函数
MyString& operator=(MyString&& str) {
// 避免自我赋值
if (this == &str) {
return *this;
}
delete[] data_;
data_ = str.data_;
str.data_ = nullptr;
return *this;
}
private:
char* data_;
int length_;
};
万能引用(也叫通用引用)
先看个例子:
template<typename T>
void func(T& t) {
cout << t << endl;
}
int main()
{
int a = 10;
func(a); // 正常,传入的是左值
func(10); // 报错,参数是个左值,但是传入的10是右值
return 0;
}
C++ 11中有万能引用(Universal Reference)的概念:使用T&&类型的形参既能绑定右值,又能绑定左值。
但是注意了:只有发生类型推导的时候,T&&才表示万能引用;否则,表示右值引用。
所以上面的可以修改为:
template<typename T>
void func(T&& t) {
cout<< t << endl;
}
int main()
{
int a = 10;
func(a);
func(10);
return 0;
}
引用折叠
一个模板函数,根据定义的形参和传入的实参的类型,我们可以有下面四中组合:
- 左值-左值 T& & # 函数定义的形参类型是左值引用,传入的实参是左值引用
- 左值-右值 T& && # 函数定义的形参类型是左值引用,传入的实参是右值引用
- 右值-左值 T&& & # 函数定义的形参类型是右值引用,传入的实参是左值引用
- 右值-右值 T&& && # 函数定义的形参类型是右值引用,传入的实参是右值引用
但是C++中不允许对引用再进行引用,对于上述情况的处理有如下的规则:
所有的折叠引用最终都代表一个引用,要么是左值引用,要么是右值引用。
规则是:如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用。
即就是前面三种情况代表的都是左值引用,而第四种代表的右值引用。
完美转发
老规矩,先看例子:
template<typename T>
void func(T& t) {
cout << "左值" << endl;
}
template<typename T>
void func(T&& t) {
cout << "右值" << endl;
}
template<typename T>
void Test(T&& t) {
func(t);
}
int main()
{
int a = 10;
Test(a);
Test(10);
return 0;
}
输出结果:
左值
左值
这里就已经和预期的不一样了,预期应该第二个输出右值。
Test()函数本身的形参是一个万能引用,即可以接受左值又可以接受右值;第一个Test()函数调用实参是左值,所以,Test()函数中调用func()中传入的参数也应该是左值;第二个Test()函数调用实参是右值,根据上面所说的引用折叠规则,Test()函数接收的参数类型是右值引用,那么为什么却调用了调用func()的左值版本了呢?这是因为在Test()函数内部,左值引用类型变为了右值,因为参数有了名称,我们也通过变量名取得变量地址。
那么问题来了,怎么保持函数调用过程中,变量类型的不变呢?这就是我们所谓的“完美转发”技术,在C++11中通过std::forward()函数来实现。我们修改我们的Test()函数如下:
template<typename T>
void func(T& t) {
cout << "左值" << endl;
}
template<typename T>
void func(T&& t) {
cout << "右值" << endl;
}
template<typename T>
void Test(T&& t) {
func(std::forward<T>(t));
}
int main()
{
int a = 10;
Test(a);
Test(10);
return 0;
}
输出结果:
左值
右值
可以输出预期的结果。