🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
文章目录
- 左值引用和右值引用
- 1.左值与左值引用
- 2.右值与右值引用
- 3.左值引用和右值引用比较
- 左值引用引用右值
- 右值引用引用左值
- 4.右值引用之后的变化
- 5.左值引用使用问题
- 6.移动构造
- 7.移动赋值
- 8.STL变化
- 9.完美转发
- 10.默认移动构造和移动赋值
- 编译器默认生成移动构造和移动赋值的条件
左值引用和右值引用
之前学过的引用几乎都是左值引用,但是无论是左值引用还是右值引用,都是给对象取别名
1.左值与左值引用
左值可以是变量或者表达式,左值引用的真正特点就是能够去取它的地址,本来也应该算上可以赋值这个特点,但是const修饰的左值不可以,所以可以取地址的就是左值
那么左值引用就是对左值取别名
int a;//左值
int &b=a;//左值引用
左值既可以出现在等号左边也可以出现在赋值符号右边
2.右值与右值引用
右值也是一个变量或者表达式,右值引用只能出现在赋值符号右边,因为它不能够被取地址,所以无法被赋值。右值一般就是字面量和表达式返回值的临时对象
double x=1.1,y=2.2;
//以下的都是右值
10;
x+y;//字面量
fmin(x,y);//返回值的临时对象
右值引用:对右值取别名,右值引用是两个&&
double x=1.1,y=2.2;
//以下的都是右值
10;
x+y;//字面量
fmin(x,y);//返回值的临时对象
//右值引用
int&& rr1=10;
double&& rr2 = x+y;
double&& rr3 = fmin(x+y);
3.左值引用和右值引用比较
左值引用引用右值
一般左值引用是不能给右值引用取别名的,因为左值引用的表达式需要可以取地址和赋值
double&l = x+y/*右值*/;//报错
但是,const左值引用是可以引用右值的
const double&r = x+y;//编译通过
因为C++11之前,没有右值引用,那怎么办呢?所以就用const左值引用的方式来引用右值,所以也推荐拷贝构造和赋值运算符重载写成const左值引用,因为不仅仅可以解决权限扩大缩小的问题,而且可以通过const左值引用来引用右值。
右值引用引用左值
普通的右值引用也是不能引用左值的
int c =10;
int && r = c;//报错
但是,可以用move来让编译器把左值识别成右值
int c=10;
int &&r = move(c);
//这样就可以了
4.右值引用之后的变化
右值是不能够取地址的,但是给右值被右值引用了之后,会导致右值被存储到特定位置,且可以取到该位置的地址。也就是说:
10;
double x=1.1,y=2.2;
int&rr1=10;
const double && rr2=x+y;
上面的rr1和rr2都是左值,这也会牵扯到完美转发的问题
5.左值引用使用问题
左值引用解决问题:
①做参数:a.减少拷贝,提高效率 b.做输出型参数
②做返回值:a.减少拷贝,提高效率 b.引用返回可以修改返回对象(比如map的operator[],修改Value)
左值引用的问题:在函数类创建的临时对象需要返回,那只能传值返回,就做不到减少拷贝,提高效率。所以有些场景是左值引用做不到的
如果说要求vector<vector<int> generate(int numRows){}>
这里需要减少拷贝,怎么做?
①全局vector,但是全局变量会有线程安全问题,不安全
②new 返回vector<vector>的指针,但是忘记释放就会内存泄漏,或者用RAII的思想来搞
③输出型参数 void generate(int numRows,vector<vector<int>>&vv);
④利用移动构造(下面讲)
6.移动构造
C++把右值分为了两种
1.内置类型的右值 – 纯右值
2.自定义类型的右值 – 将亡值(通常都是一些临时对象,生命周期就在这一行)
//拷贝构造
string(const string& s)
:_str(nullptr);
{
string tmp(s);
swap(tmp);
}
//移动构造
string(string&&s)//因为s不可取地址,不可赋值,所以不加const
:_str(nullptr)
{
swap(s);//因为s是右值,将亡,所以直接交换
}
因为有资源的转移,所以叫移动构造
string str1(move(str1));
string str2(string("hello"));
移动构造比拷贝构造的代价小太多,拷贝构造需要先构造一个临时变量(而这个构造是深拷贝,当然也可以用引用计数和写时拷贝来进行优化),然后将这个临时变量再拷贝构造给原来的变量。而移动构造在函数内部就资源转移了,只是交换了一下管理的指针。
编译器也是很聪明的,有const左值引用的拷贝构造和右值引用的移动构造,都可以引用右值,但是确是调用的移动构造,因为它能够识别到这是一个将亡值。挑选最匹配的去调用
7.移动赋值
string ret;
ret = to_string(1234);
//这时候就需要移动赋值
//拷贝赋值
string& operator=(const string&s)
{
string(tmp);
swap(tmp);
return *this;
}
//移动赋值
string& operator=(string&&s)//构造
swap(s);//s将亡
return *this;
}
8.STL变化
C++11以后,STL就提供了移动构造和移动赋值
右值引用除了移动构造和移动赋值以外,insert也用到了
还有就是增加了emplace
传递多个左值/右值(因为会引发万能引用/引用折叠)来进行插入
9.完美转发
先来看一个现象
右值引用&&和模板在一起,就会引发一个东西叫做 万能引用/引用折叠
引用折叠的意思就是传左值过去的时候,会把&&折叠成&,以对左值进行兼容(有了引用折叠,其实就可以替代单纯的左值引用,但是为了向前兼容,左值引用还存在)
然后再上面也说过,右值引用会把右值存在一个特定的位置,导致右值的别名是一个左值(因为可以对这个别名取地址)
所以上面就可以看到都是左值引用的结果
C++11中就提供了一个东西—>完美转发
可以让别名t保持它原有的属性,就可以做到去调用右值引用
完美转发的意思就是你把我给你的东西原封不动的交给别人,不要改变它的属性
在一个调用链中连续调用就会用到,比如说list的push_back
void push_back(const T& x)
{
// ...
}
void push_back(T&&x)//引用折叠,既可引用左值也可引用右值
{
}
push_back调用的是insert,如果不进行完美转发,那么push_back传给insert的就是左值,然后就又会去调用拷贝构造,并不会调用移动构造,效率也不会得到提升,在整个调用链上都需要完美转发一下
10.默认移动构造和移动赋值
我们以前知道的默认成员函数有六种:①构造函数 ②拷贝构造函数 ③赋值运算符重载函数 ④析构函数 ⑤取地址运算符重载函数 ⑥const取地址运算符重载函数
C++11之后,新增添了两个 ①移动构造 ②移动赋值
编译器默认生成移动构造和移动赋值的条件
①如果没有实现移动构造函数,且没有自己实现析构函数,拷贝构造,拷贝赋值重载的任意一个(三个都不写才会生成,但是这三个只要有一个写了,基本都要写),那么编译器才会自动生成,并且,默认移动构造对于内置类型对象进行值拷贝,自定义类型对象,则看这个对象是否实现移动构造,如果实现就调用它的移动构造,没有实现就调用拷贝构造。
②移动赋值同理