目录
概念
左值
左值是一个具有内存位置并且可以被取地址的表达式。
右值
右值是一个没有内存位置或临时性的表达式
简单来说,可以取地址的就是左值,不可以取地址的就是右值。
左值右值区分
下面都是左值:
下面都是右值:
总结:
1、左值引用通常是具有持久性的对象或者变量。例如:变量、数组元素、引用等都是左值。左值具有名字,可以被多次引用。
2、右值通常是临时创建的、没有名字的对象或值。右值只能出现在赋值运算符右边,不能被取地址。例如,字面量、临时对象、函数返回值、表达式等都是右值。右值在表达式中通常是一次性的,且不能被修改。、
左值引用和右值引用
左值引用就是给左值取别名,右值引用就是给右值取别名。
左值引用使用的符号是&
右值引用使用的符号是&&
下面是左值引用:
下面是右值引用:
右值引用的价值
额外知识:编译器优化
观察上面的函数,我们要返回s,并且在main函数中拿s接受它。
我们知道,在f()函数中的s的生命周期只在函数里面,而想要被main函数中的s接收到,就只能返回拷贝。而如果返回的内容比较大,那么拷贝的内容就大,效率就会大大降低。
上面这种情况,在编译器视角是怎么进行的呢?
f()函数中的s将内容拷贝一份,生成临时变量,临时变量将内容内容再拷贝给main()函数中的s。拷贝了两次。
编译器可能会存在优化:当代码写在同一行的时候,并且存在两次拷贝构造,编译器就会优化成一次拷贝构造。如下图所示。
为了进一步讲解,我手写了一个自己的string类
namespace zyy
{
class string
{
public:
//构造函数
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
//拷贝构造
string(const string& str) //传的引用
:_str(nullptr)
{
cout << "string(const char* str) -- 拷贝构造" << endl;
string tmp(str._str);
swap(tmp);
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
};
我们使用自己实现的string类进行测试:
1、不写在同一行,不存在编译器优化的情况:
2、写在同一行,编译器优化之后:
右值引用减少拷贝
在string类中加入下面这样的移动构造
看string的实现也可以发现:
在加入了移动拷贝之后继续使用之前的代码进行测试:
进行对比:
左值引用可以给右值取别名
大多数文章只是告诉你调用了移动构造,但是却没有说原理,我这里将原理讲清楚:
左值引用可以给右值取别名,而右值的别名是什么?就是右值引用!
观察上面,可以明显看到,10和fmin(x,y)都是右值,那么为什么int& c作为左值引用却报错了?
因为,这两个表达式拿给int& c和int& b的时候,返回的是拷贝,产生了临时变量,不是他们本身给int& c或者int& b,而是把临时变量拿给int& c或者int& b。
临时变量具有常属性,所以,普通的左值引用会报错,必须是const左值引用。
所以,下面这样就不会报错了。
观察string类中的两个拷贝构造函数
移动构造 和 移动赋值
继续说回移动构造、移动赋值
内置类型的右值叫做纯右值
自定义类型的右值叫做将亡值
移动构造和移动拷贝的本质是什么?
是将 将亡值 的内存转换给新的要来接收的值
std::move()
move()的作用就是将左值转换成右值。
调用pb(move(pa))的时候,就会出现pb指向了pa的位置。不过这种情况一般都不怎么常用
常用的是s2(move(s1)),这样,s2获得了s1的内容,但是却没有发生拷贝。
完美转发
先看下面的情况:
那么,如果我们想要调用右值引用就是右值引用呢?该怎么办?
但是这样很麻烦,首先,不能左值和右值一直调用。其次,会改变右值引用本来的左值属性。
而完美转发,就是来解决这种问题的!
完美转发是指在C++中通过保持传递参数的值类别(左值或右值)和常量性,将参数传递到另一个函数或对象的能力
这样理解太抽象:
在C++11之后,引入了右值引用和std::forward
模板函数,使得实现完美转发变得更加容易。通过使用std::forward
函数,可以在传递参数时保持其原始值类别和常量性,从而实现完美转发。
使用完美转发: