前言
希望通过本篇文章能让你了c++右值引用的目的,以及其和移动语义、完美转发的联系及用法!
一、右值引用
1.左值和右值
到底什么时候是左值?什么时候是右值?是不是有点混乱?
在 C++ 中,每个表达式(expression)都有两个特性:
- has identity? —— 是否有唯一标识,比如地址、指针。有唯一标识的表达式在 C++ 中被称为glvalue(generalized lvalue)(广义左值)。
- can be moved from? —— 是否可以安全地移动(编译器)。可以安全地移动的表达式在 C++ 中被成为 rvalue。
根据这两个特性,可以将表达式分成 4 类:
- has identity and cannot be moved from - 这类表达式在 C++ 中被称为 lvalue。
- has identity and can be moved from - 这类表达式在 C++ 中被成为 xvalue(expiring value)(到期值)。
- does not have identity and can be moved from - 这类表达式在 C++ 中被成为 prvalue(pure rvalue)(纯右值)。
- does not have identity and cannot be moved -C++ 中不存在这类表达式。
简单总结一下这些 value categories 之间的关系:
- 可以移动的值都叫 rvalue,包括 xvalue 和 prvalue。
- 有唯一标识的值都叫 glvalue,包括 lvalue 和 xvalue。
- std::move 的作用就是将一个 lvalue 转换成 xvalue。
2.右值的语法
1.对常量(右值)右值引用
int num = 10;
//int && a = num; //右值引用不能初始化为左值
int && a = 10; //右值引用能初始化为常量
a=100; //右值引用能对右值进行修改
const int && a=100;//c++允许常量右值引用 编译器不会报错
2.对于可移动的左值的右值引用 在此之前先介绍std::move函数,move 本意为 “移动”,但该函数并不能移动任何数据,它的功能很简单,就是将某个左值强制转化为右值。
这里就要注意:move实际上是一种资源的转移,可以理解为一个对象对另一个对象资源的窃取,被窃取的对象在后续不能被继续拿来使用。
int a=10;
int &&b=std::move(a);//a本身是一个左值 move 强制将左值转化为右值
3.右值引用的提出是是为了实现后面的移动语义和完美转发。
3.将亡值
在C++11之前的右值和C++11中的纯右值是等价的。C++11中的将亡值是随着右值引用的引入而新引入的。换言之,“将亡值”概念的产生,是由右值引用的产生而引起的,将亡值与右值引用息息相关。所谓的将亡值表达式,如下列表达式:
1)返回右值引用的函数的调用表达式 2)转换为右值引用的转换函数的调用表达
二、移动构造和移动赋值
1.对于获取另一个对象的资源,我们在c++11之前采用的都是对别人资源进行拷贝,自己需要有自己的内存来存储复制过来的资源。
2.c++11之后有了移动语义,对于获取另一个对象的资源更像是对象的资源转让,用更加形象的词来描述是一个对象对另一个对象资源的“窃取”,像是形成了一种规定:一个对象把另一个对象的资源占为己有,另一个对象失去原来的资源也不会去使用它。
3.移动语义的具体实现是通过类中的移动构造和移动复制来实现的。
当类中有主动申请堆区内存的指针为成员变量的时候,移动语义的便捷性就体现的淋漓尽致!!!
#include <iostream>
class Person
{
private:
int *m_age;
public:
//无参构造
Person(){}
//拷贝构造 传左值常量引用
Person(const Person & p): m_age(new int(*(p.m_age))){}
//移动构造 传右值引用
Person(Person&& p):m_age(p.m_age)
{p.m_age= nullptr;}
//移动赋值运算符 传右值引用
Person& operator=(Person&&p){
if(this==&p||p.m_age== nullptr) return *this;
else {this->m_age=p.m_age;p.m_age= nullptr;return *this;}
}
//析构函数
~Person(){delete m_age;}
};
int main()
{
Person s;
//move函数强制将左值转换为右值
Person s2(std::move(s));//调用移动构造函数
Person s3=std::move(s2);//调用移动赋值运算符重载函数
}
三、完美转发
1.首先解释一下什么是完美转发,它指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。
2.c++11之后的完美转发实际上是利用了右值引用的万能性,既能够传左值又能传右值,所以右值引用又称万能引用。
3.无论函数模板传的值是左值还是右值,我们都需要解决另一个问题,如何将函数模板接收的形参,连同其属性一起传递给被调用的函数,要使用到std::forward()模板函数,它可以很好地解决这个问题。
#include <iostream>
//测试结果
void show(const int &a)
{
std::cout<<"rvalue"<<std::endl;
}
void show(int &a)
{
std::cout<<"lvalue"<<std::endl;
}
//完美转发函数模板
template<typename T>
void func(T&&t)
{
show(std::forward<T>(t));//左值、右值及其属性。
}
int main()
{
func(10);//右值
int t=10;
func(t); //左值
}
测试结果:
rvalue lvalue
总结
总而言之,右值引用是一个万能引用,它的出现是为了实现移动定义和c++11之后的完美转发!
另外,这是来自一个小白c++学习者的第一篇博客,尝试着分享自己的所得,希望能和大家一起进步,有啥error望大家指出。
右值定义参考:添加链接描述