传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
在以前我们知道有引用的语法,我们通常叫做左值引用,那么什么是左值呢?可以看下边的几个例子
int main()
{
// 左值:可以取它的地址
/*int a = 10;
const int b = 20;
int* p = &a;
*p = 100;*/
// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
double x = 1.1, y = 2.2;
}
可以看到,左值,可以获取它的地址+可以对它赋值。当然定义时用const修饰的左值,也不能对他赋值,但是可以取地址。左值引用呢,就是给左值的引用。
知道左值后,那么什么是右值呢?右值的形式是什么?右值有什么实际价值?看几个例子
int main()
{
// 右值:不能取地址
10;
x + y;
fmin(x, y);
//cout << &10 << endl;
//cout << &(x + y) << endl;
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
// 这里编译会报错
10 = 1;
x + y = 1;
fmin(x, y) = 1;
}
以上几个例子都是右值,右值也是一个表达数据的表达式,如字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号右边,但是不能出现在赋值符号的左边,右值不能取地址。
右值引用就是对右值的引用,给右值取别名
左值引用可以引用右值吗?右值引用可以引用右值吗?
// 有条件的支持
// 左值引用可以引用右值吗?const的左值引用可以
//double& r1 = x + y;
const double& r1 = x + y;
// 右值引用可以引用左值吗?可以引用move以后的左值
//int&& rr5 = b;
int&& rr5 = move(b);
这里注意:
11是字面常量,Func函数参数列表是个左值引用,所以会报错,两种修改方法:
第一种方法:const的左值引用可以接收右值
// x既能接收左值,也能接收右值
template<class T>
void Func(const T& x)
{}
rr1和rr2可以取地址了,它们是左值了。
引用的价值:减少拷贝
左值引用解决哪些问题?
但是,C++98的左值引用面向下边的场景很难进行处理:
右边的写法虽然解决了问题,但是并不是太符合使用习惯
不加移动构造的string
加移动构造的string类
不加移动构造移动赋值的string
加移动构造移动赋值的string类
移动构造和移动赋值解决了传值返回这些类型对象的问题,STL的各个容器在C++11增加移动构造和移动赋值。移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
在模板中,&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值。
先看个例子:
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(t);
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}
在我们的预期中,Func函数应该是,左值打印左值,右值打印右值,但是运行结果确如下图所示:
这里可以看到,所有的都成了左值引用,根本没有调用右值引用的版本,这个就是引用折叠的问题。
我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用完美转发。
针对上边的代码只需要将,T类型的参数t完美转发一下就可以了,std::forward 完美转发在传参的过程中保留对象原生类型属性
template<typename T>
void PerfectForward(T&& t)
{
Fun(std::forward<T>(t));
}
运行结果如下:
可以看到完美转发的效果,左值调用左值,右值调用右值。
在我们模拟实现的list中测试,移动构造
list插入"world"(右值),代码调用层级如下
void push_back(T&& x)
{
insert(end(), std::forward<T>(x)); //完美转发保留右值属性
}
// 调用insert,这里也是万能引用接收
iterator insert(iterator pos, T&& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
//这里调用节点的构造函数,也同样要完美转发,保留右值属性
Node* newnode = new Node(std::forward<T>(x));
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
// insert调用了链表节点的构造,同样用万能引用接收
list_node(T&& x)
:_data(std::forward<T>(x)) //完美转发
, _next(nullptr)
, _prev(nullptr)
{}
最后的节点的构造,会调用string类的构造,所以那里同样需要完美转发,调用了string类的右值构造
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(string&& s) -- 资源转移" << endl;
swap(s);
}