右值引用和Lambda表达式
C++是一个非常追求效率的语言,在C++11中不仅增加了unordered_set和unordered_map形容器,对已有的string、vector、list、map等也增加了移动构造和移动赋值,价值非常大,进一步提高了效率。
1. 右值引用
C++98中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通过指针来实现的,因此使用引用,可以提高程序的可读性。为了提高程序运行效率
,C++11中引入了右值引用,右值引用也是别名,但其只能对右值引用。
- 左值(原来所接触的引用):可以放到 “=” 左边的值一般就是可修改的值
- 右值:一般是不可以修改的一些值。比如:常量、临时变量、返回值
#include<iostream>
using namespace std;
int main()
{
int val = 10;
int& lr1 = val; //左值引用一般是给左值取别名,修改lr1也就相当于修改了val
const int& lr2 = int(1); //左值引用也可以给右值取别名,但是右值一般都是不可以修改的值,所以要加上const
int&& rr1 = int(1);//右值引用一般是给右值取别名
int&& rr2 = move(val);//右值引用不能直接给左值取别名,但是可以给move(左值)取别名,c++11增加的移动语义
}
对于上述代码的总结就是:对于一般情况下左值引用就是给左值取别名,但是加上const左值引用也可以给右值取别名。对于右值引用一般是给右值取别名,但是给左值加上move,就相当于改变了左值的属性,使其变为了右值的将亡值,所以右值引用也可以给move后的左值取别名。
1.1 为什么要引入右值引用?
其实右值引用最大的作用是能够让编译器区分出来左值引用还是右值引用,进而让他能够去适配拷贝构造函数还是移动构造函数。
如果一个类中涉及到资源管理,用户必须显式提供拷贝构造、赋值运算符重载以及析构函数,否则编译器将会自动生成一个默认的,如果遇到拷贝对象或者对象之间相互赋值,就会出错,比如:
class String
{
public:
String(char* str = "")
{
if (nullptr == str)
str = "";
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
cout << "String(const String& s)" << endl;
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
cout << "String& operator=(const String& s)" << endl;
if (this != &s)
{
char* pTemp = new char[strlen(s._str) + 1];
strcpy(pTemp, s._str);
delete[] _str;
_str = pTemp;
}
return *this;
}
String operator+(const String& s)
{
char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
strcpy(pTemp, _str);
strcpy(pTemp + strlen(_str), s._str);
String strRet(pTemp);
return strRet;
}
String& operator+=(const String& s)
{
//......
return *this;
}
~String()
{
if (_str) delete[] _str;
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2("world");
String s3 = s1 + s2; //对于s1和s2为自定义类型,调用operator+,然后返回值在出作用域的时候生命周期将会到,所以要先调用一次拷贝构造
//产生一份临时的,然后在拿这份临时的去拷贝构造s3
cout << endl;
String s4 = s1 += s2;//对于s1和s2为自定义类型,调用operator+=,然后返回的是引用,所以在调用一次拷贝构造
//生成一个临时值然后在调用一次operator=
cout << endl;
String s5;
s5 = s1 += s2;
return 0;
}
- 在operator+中:strRet在按照值返回时,必须创建一个临时对象,临时对象创建好之后,strRet就被销毁了,最后使用返回的临时对象构造s3,s3构造好之后,临时对象就被销毁了。仔细观察会发现:strRet、临时对象、s3每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完全相同的对象,对于空间是一种浪费,程序的效率也会降低,而且临时对象确实作用不是很大,那能否对该种情况进行优化呢?下面将引入右值引用。
结论:传值返回,会多一次拷贝,引用返回,会少一次拷贝(但是从上述代码来看,调用operator+和operator+=好像并没有看出来任何的区别,那是因为编译器进行了优化,实时上,传值返回是会调用两次拷贝构造的,引用返回只需要调用一次拷贝构造)
左值引用作用:
- 函数引用传参—减少拷贝+输出型参数
- 函数引用传返回值—减少拷贝
核心作用:提高效率+提高程序可读性(不需要复杂指针)
//移动构造---移动拷贝
//右值分为1.纯右值2.将亡值
String(String&& s)
:_str(nullptr)
{
cout << "String(String&& s)" << endl;
std::swap(_str, s._str);
}
//移动赋值 --- 移动赋值
String& operator=(String&& s)
{
cout << "String& operator=(String&& s)" << endl