引入
本文接着来介绍cpp11更新的一些新特性
提前说明一下,左值引用和右值引用都是对于对象取别名
左值引用与右值引用
左值引用
我们一般情况下可以对其进行取地址,并且还可以对其进行赋值,左值可以出现在等号的左边
给出一段例子
int main()
{
// 以下的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;
return 0;
}
右值引用
右值
- 右值与左值相同(都是一个数据的表达式)
- 字面常量、表达式返回值、返回值不是左值引用的函数返回值(临时对象)
- 右值不能取地址
int main()
{
double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
10; x + y;
min(x, y);
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = min(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
//10 = 1; x + y = 1;
//min(x, y) = 1;
return 0;
}
二者比较
- 语法上的区别就是一个&和两个&
- 二者都是取别名,不消耗额外空间
- 左值引用指针存储的是当前左值的地址
- 右值引用先把右值拷贝到栈上的一个临时空间,然后其指针存储这个临时空间的地址
左值引用
- 左值引用只能引用左值
- const左值引用可以引用右值
右值引用
- 右值引用只能引用右值
- 右值引用可以引用move后的左值
int main()
{
// 右值引用只能右值,不能引用左值。
int&& r1 = 10;
// error C2440: “初始化”: 无法从“int”转换为“int &&”
// message : 无法将左值绑定到右值引用
//int a = 10;
//int&& r2 = a;
// 右值引用可以引用move以后的左值
int&& r3 = std::move(a);
return 0;
}
右值引用的意义
左值引用的使用场景
一般用作参数和返回值
void func1(bit::string s){}
void func2(const bit::string& s){}
int main()
{
bit::string s1("hello world");
// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
func1(s1);
func2(s1);
// string operator+=(char ch) 传值返回存在深拷贝
// string& operator+=(char ch) 传左值引用没有拷贝提高了效率
s1 += '!';
return 0;
}
短板:
当其返回值为临时对象,出了作用域就不存在了,所以无法用左值引用,这样就会导致至少一次的拷贝构造
右值引用的使用场景
移动构造
我们可以用右值引用和移动语义来解决多出的拷贝构造
我们在类中增加移动构造函数,目的是窃取要拷贝对象的资源,这样就不需要深拷贝了,减少了资源的浪费
// s1.swap(s2)
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
//这里的::意思是
//::swap 意味着调用全局命名空间中的 swap 函数,而不是当前命名空间中的 swap 函数。
}
// 移动构造
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(string&& s) -- 移动语义" << endl;
swap(s);
}
补充:当既有拷贝构造又有移动构造时,编译器会自动选择合适的(跟之前的一些知识比较像:只吃自己想吃的好吃的😋)
移动赋值
有移动构造就有移动赋值
就是右值引用版本的operator=
万能引用
经过上面的学习,感觉右值引用很厉害,减少了资源消耗,提高了效率,
值得一提的是:
编译器在早期对于返回值的传递进行了优化,本来是一次拷贝构造+一次移动构造完成返回值的传递,优化后先将返回值move一下,之后再移动构造
但这样会有问题:之前所有传值返回的代码都要加一个move,这就很麻烦了,所以编译器是进行了隐式的move
那有没有更简便的写法呢,这就要结合我们的老朋友:模板了
结合模板的引用,使得在引用时,能保留对象的原生属性
也叫通用引用、引用折叠
用&&结合模板使用,
则传入左值就是左值,传入右值就是右值
完美转发
语法形式:
std::forward<T>(t)
可以大概认为编译器做了识别,保持其原来的属性
与move的区别是,move是确定最后是右值,而forward是保持其原生属性
总结
- 左值和右值是什么,可不可以修改
- 万能引用怎么用
- 完美转发和move的区别是什么
下一篇文章将介绍可变参数模板、lambda的相关知识