目录
左值与右值
左值引用与右值引用
右值引用的作用
移动构造和移动赋值
合成的移动操作
万能引用和完美转发
左值和右值都是一个表示数据表达式,和它们一样,左值一般出现在赋值表达式的左边(右边也可以)右值出现在赋值表达式的右边(不能出现在左边)
我们可以看一下几个表达式来明确左值和右值的区别:
//左值 a\b\*p 都是左值,可以长时间存在
const int a = 100;
double b = 10.0;
int* p = &a;
//右值 10\x+y\func(x) 都是右值,可以看作临时变量
10;
x + y;
func(x);
我们可以根据上方的表达式来总结左值和右值最大的区别:
1.左值有持久的状态,右值要么是字面常量、要么是求值过程中创建的临时变量
2.左值可以取地址,右值不可以
来看一段左值引用和右值引用的代码:
//左值引用
int& c = a;
int& d = *p;
//右值引用
int&& c2 = 10;
int&& d2 = a + b;
可以看出,左值引用和右值引用在语法上最大的差别是&和&&的差别
但是左右值引用在定义上也有很大的差别:
1.左值引用可以看作平时我们使用的引用,作为一个对象的别名
2.右值引用只能绑定到一个将要销毁的变量(“将亡值”)
那么左值引用可以引用右值,右值引用可以引用左值吗?
答案是可以的,只是我们需要对语法进行一点改变:
//两个左值
int a = 1;
int b = 2;
//左值引用 引用 右值
const int& c = 20+b;
//右值引用 引用 左值
int&& d = move(a);
int&& e = move(b);
即:
左值引用 引用 右值时,需要加上const
右值引用 引用 左值时,需要使用move()函数将左值转为右值
我们知道左值引用的一个作用是用别名来减少编译器的拷贝,提高效率。
那么C++11右值引用的作用是什么呢?
因为右值引用只能指向一个“将亡值”,我们可以在这个将亡值要销毁的时候把它内部的数据和需要用拷贝构造或拷贝赋值的对象进行交换,这样,这个对象的原数据一起被将亡值带走,这个对象也获得了它想要的数据。我们也可以减少拷贝,提高效率
目的是为了实现更高的效率和减少更多的拷贝:实现移动赋值和移动构造
让我们来看看代码:
首先我们先定义一个Student类,作为移动构造和移动赋值的基础,并在内部写一个移动构造,传的是一个右值引用
ps:因为右值引用的值在出了这个作用域后就会消亡,所以我们不需要另外开一个空间来把this指向的对象和拷贝进来的对象进行交换
//我们定义一个Student类
class Student
{
public:
//普通构造
Student(const string& n,const int& a) :name(n), age(a)
{}
//默认构造
Student(){}
//拷贝构造
Student(const Student& s)
{
Student tmp(s.name, s.age);
swap(tmp);
cout << "拷贝构造" << endl;
}
//移动构造
Student(Student&& s)
{
swap(s);
cout << "移动构造" << endl;
}
private:
//重载了一下swap函数
void swap(Student& s)
{
name.swap(s.name);
std::swap(age,s.age);
}
private:
string name;
int age;
};
再在main函数中创建Student:
//移动构造和拷贝构造
Student one("张三", 20);
//创建一个链表存储Student类
//简介调用拷贝构造和移动构造
list test1;
test1.push_back(one);
test1.push_back(Student("王五", 123));
cout << "--------------------------" << endl;
//直接调用拷贝构造和移动构造
Student two(one);
//用move把左值变成右值,使用移动构造后one就变为空
Student three(move(one));
运行结果如下:
移动赋值和移动构造的区别在于:移动赋值通过重载=运算符来实现资源转移,本质和移动构造相似,不做赘述。
和拷贝构造函数一样,C++11后,编译器也会为某些类默认生成移动构造函数和移动赋值函数,但前提是这个类没有显式定义自己的拷贝构造、拷贝赋值运算符和析构函数,如果一个类中显式定义了这些它们,那么编译器就不会默认生成合成的移动构造和合成的移动赋值
PS.C++11前,一个空类会默认生成的有构造函数、析构函数、拷贝构造函数、赋值运算符重载、两个&运算符重载,C++11后会默认生成构造函数、析构函数、拷贝构造函数、赋值运算符重载、两个&运算符重载和移动构造函数、移动赋值运算符重载
如果我们想用一个函数同时引用左值和右值,同时又不使用const和move把原来的数据变成右值或左值时,我们应该怎么办呢?
这里学习一个新概念:万能引用和完美转发
//这就是一个万能引用
template
void PerfectForward(T&& t)
{
//先写一个临时变量在里面,方便等会调用查看
Student a(t);
}
模板+右值引用,就是万能引用的一个格式
此时,传进这个函数的值会被自动识别为左值或右值,然后再传给拷贝构造或移动构造函数
我们来传一传:
PerfectForward(one); //传左值
PerfectForward(Student("李四", 24)); //传右值
最后运行结果却是两个拷贝构造函数?
主要是因为形参t会变成左值,就算传进来的是右值,编译器也会把它看成左值(看它单传一个t是不是很像左值。。。)
我们想要解决这个问题就必须进行完美转发,在完美转发下,传进来的是谁,传给那个构造函数的就是谁:
template
void PerfectForward(T&& t)
{
//forward<类型>()
//完美转发
Student a(std::forward(t));
}
此时编译器就会好好调用应该调用的拷贝构造和移动构造了: