目录
概念:
左值引用和右值引用
概念:
注意:
左值引用的意义
作函数参数
函数引用返回
右值引用的意义
诞生背景
移动构造
移动赋值
其他应用
万能引用和完美转发
默认的移动构造和移动赋值
概念:
左值:顾名思义,可以在等号左边出现的被叫做左值,左值可以取地址。
右值:只能在等号右边出现的值为右值,且右值不能取地址。
例如
//左值 int a = 2; string s="hello"; const int b=3; int* pa=&a; //右值 3;//字面常量 a + 2;//表达式 add(2, 3);//函数返回值
左值引用和右值引用
概念:
左值引用是用来引用左值的,给左值起别名:
int a = 2;//左值 int& ra = a;//左值引用
右值引用是用来引用右值的,给右值起别名:
int&& rc = 5;//右值引用
注意:
左值引用只能引用左值,因为如果引用右值会导致权限的放大:
int& ra = 2;//权限放大,编译错误
但是左值引用加上const就可以引用右值:
const int& ra = 2;
右值引用只能引用右值,但是可以引用move后的左值
int a = 2;//左值 int&& ra = move(a);
左值引用的意义
作函数参数
C++引用底层是C语言的指针,所以在作为函数参数时可以减少拷贝或者作为输出型参数使用。
void func(const string& s)//输入型参数,const&既可以减少拷贝又可以防止改变引用的实体 {cout << s << endl; } void add(int& a)//作为输出型参数 {a=2; }
函数引用返回
出了函数栈帧,返回变量不会销毁的就可以用引用返回,减少拷贝。否则会导致内存越界。
string ss="hello"; const string& func() {return ss;//ss是全局变量,出函数栈帧不会销毁,用引用返回就不会生成临时变量 }
右值引用的意义
诞生背景
普通函数传值返回时,由于函数栈帧出作用域会销毁,其内定义的变量会被释放,所以会返回一个临时对象,这个临时对象是要返回对象的拷贝。
此时,我们会想:既然局部变量s马上就要销毁了,那么我们是否可以将它的资源交换给临时变量呢,这样临时变量的创建就不需要耗费拷贝的代价了。
C++把这种即将要销毁的自定义对象称为将亡值,在拷贝将亡值的时,可以利用右值引用在拷贝构造和赋值运算符重载函数中接收将亡值,并实现资源的置换,进而减少无意义的拷贝。
移动构造
在拷贝构造的实现中,实现一份形参使用右值引用接收的拷贝构造(即移动构造),当使用将亡值拷贝构造时,编译器会自动匹配移动构造,在移动构造内实现资源的互换。
移动赋值
在赋值运算符重载的实现中,实现一份形参使用右值引用接收的赋值运算符重载(即移动赋值),当使用将亡值赋值时,编译器会自动匹配移动赋值,在移动赋值内实现资源的互换。
C++11后,基本所有的STL容器都新增了移动构造和移动赋值,以此来提高需要深拷贝的容器是将亡值时的拷贝效率。
其他应用
除此之外,右值引用在其他接口中也大放异彩:
STL容器插入数据的接口:当插入需要深拷贝对象的右值时,减少拷贝
std::swap交换函数:
万能引用和完美转发
当函数模板和右值引用组合:万能引用既可以引用左值,又可以引用右值
万能引用又叫做引用折叠,即传入右值时&&,传入左值时折叠&。
右值引用的作用就是限制了接收变量只能是右值,但是右值引用本身会退化为左值:
template<class T>
void func(T&& val)//万能引用
{cout << val << endl;//就算原本接收的是右值,右值引用val退化为左值
}
int main()
{func(10);return 0;
}
完美转发:保留对象原生类型属性,即保持它的左值或者右值的属性
template<class T>
void func(T&& val)//万能引用
{cout << std::forward<T>(val) << endl;//这里使用完美转发保留传入之前的属性
}
int main()
{func(10);return 0;
}
默认的移动构造和移动赋值
默认移动构造:
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类
型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
如果实现了就调用移动构造,没有实现就调用拷贝构造。
解释:
分析上述定义,默认移动构造就是在析构函数 、拷贝构造、赋值重载都没有写,且移动构造也没有显式实现的情况下,编译器会默认生成。
当析构函数,拷贝构造、赋值重载都没有写的时候,且默认C++程序员没有犯错,那么这个类本身一定只含有内置类型成员或"自控"的自定义类型成员,没有需要手动释放的资源。
此时编译器就要做事了,默认生成一份移动构造,当别人调用默认移动构造,来将自定义类型中可以移动构造的对象自动调用移动构造,增加效率,非常合理。
反着思考:如果一个类内有资源需要手动释放,那么析构函数 、拷贝构造、赋值重载一定都要手动写,此时就算我们不手动写移动构造,编译器也不会默认生成,因为编译器不会知道哪些资源需要转移。
默认移动赋值完全类似。