目录
1. 右值引用的概念
C++98中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通过指针来实现的,因此使用引用,可以提高程序的可读性。
为了提高程序运行效率,C++11中引入了右值引用,右值引用也是别名,但其只能对右值引用。
左值和右值
- 左值一般可以修改,可以取地址(一般就是我们定义的变量,int a = 0;)
- 右值一般不可被修改,不能取地址(一般是临时变量)
C++11对右值进行了严格的区分:
- C语言中的纯右值:比如,a+b, 100
- 将亡值:比如,表达式的中间结果、函数按照值的方式进行返回。
左值引用和右值引用
- 左值引用& -》 可以引用左值(左值引用不可以引用右值)
- 右值引用&& -》 可以引用右值(右值引用不能引用左值)
- const 左值引用 -》左值 或 右值 //const左值引用可以引用右值
- 右值引用 -》 std::move(左值) //右值引用可以引用move之后的左值
// 普通类型引用只能引用左值,不能引用右值
int a = 10;
int& ra1 = a;
//int& ra2 = 10; // 编译失败,因为10是右值
const int& ra3 = 10;//const左值引用可以引用右值
const int& ra4 = a;
// 10纯右值,本来只是一个符号,没有具体的空间,
// 右值引用变量r1在定义过程中,编译器产生了一个临时变量,r1实际引用的是临时变量
int&& r1 = 10;
int a = 10;
//int&& r2 = a; // 编译失败:右值引用不能引用左值
int && r3 = std::move(a);//右值引用可以引用move之后的左值
2. 右值引用的使用场景
2.1 移动语义
左值引用的使用场景 1、做参数 2、做返回值。
- 左值引用传参时减少了拷贝,提高了效率。
- 函数返回分两种:传引用、传值,但不是所有情况都能用传引用(比如函数返回对象出了函数作用域就不在了,叫做临时对象),只能传值,传值返回一般会发生两次深拷贝(编译器会优化为一次),这样效率低下。此时,如果类里面设计了移动构造,传值返回时就会自动调用移动构造(实际上是一种资源转移的方式),提高了效率。
右值引用并不是直接使用去减少拷贝、提高效率。而是在支持深拷贝的类里面,如string、vector、list,提供移动拷贝和移动赋值。这时候类对象进行传值返回时,会调用移动拷贝和移动赋值,来转移资源,避免了深拷贝,提高了效率。
(1)移动构造,主要是解决函数传值返回时候的深拷贝问题。
在C++11中,编译器会为类默认生成一个移动构造,该移动构造为浅拷贝,因此当类中涉及到资源管理时,用户必须显式定义自己的移动构造。
string(string&& s)//右值引用,函数返回值为临时对象时会自动匹配这个
:_str(nullptr)
, _size(0)
, _capacity(0)
{
//cout << "string(string&& s) -- 移动构造" << endl;
this->swap(s);
}
(2)移动赋值,功能和以上类似。
//移动赋值类似上面的移动构造
string& operator=(string&& s)
{
//cout << "string& operator=(string&& s) -- 移动赋值" << endl;
this->swap(s);
return *this;
}
2.2 万能引用+完美转发
完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。
函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相应实参是右值,它就应该被转发为右值。
这样做是为了保留在其他函数针对转发而来的参数的左右值属性进行不同处理(比如参数为左值时实施拷贝语义;参数为右值时实施移动语义)
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是万能引用类型的唯一作用就是扩大了接收的类型,后续使用中都退化成了左值。
若希望能够在传递过程中保持它的左值或者右值的属性, 就需要用到完美转发forward
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)
{
Fun(std::forward<T>(t));//在传递过程中保持左值或右值的属性
}
2.3 emplace_back函数
template< class... Args >
void emplace_back( Args&&... args ); //右值引用
在c++11的容器的插入接口函数中,如果实参是右值,则可以转移资源,减少拷贝
- push_back:先构造一个对象 + 再拷贝构造到尾插的位置上
- emplace_back的用法:直接传对象的参数构造到尾插的位置上
emplace_back除了用法上,和push_back没什么太大的区别。emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
std::list< std::pair<int, char> > mylist;
mylist.push_back(pair<int, char>(40, 'd'));
mylist.push_back(make_pair(40, 'd'));
mylist.push_back({ 50, 'e' });
mylist.emplace_back(make_pair(30, 'c'));
mylist.emplace_back(10, 'a');//emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
综上,右值引用的作用有
- 给中间临时变量取别名:
- 实现移动语义(移动构造与移动赋值)
- 实现完美转发
- 实现emplace_back函数
3. 可变参数模板
有些时候,我们定义一个函数,可能这个函数需要支持可变长参数,也就是说调用者可以传入任意个数的参数。比如C函数printf()
printf("name: %s, number: %d", "Obama", 1);
在C++11里面专门提供了对可变长参数的更现代化的支持,那就是可变长模板。
模板参数包(template parameter pack)
template <class ...Args>
void ShowList(Args... args)//Args就是一个模板参数包
{
cout << sizeof...(args) << endl;
//for (int i = 0; i < sizeof...(args); ++i)
// cout<<args[i]<<" "; // 不支持
}
int main()
{
ShowList();
ShowList(1);
ShowList(1, 'A');
}
那么,如何获取每一个参数呢?
template <class T>
void ShowList(const T& t)
{
cout << t << endl;
}
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
cout << value << " ";
ShowList(args...);
}