目录
- 概念
- 左值和右值
- 左值引用和右值引用
- 使用
- 左值引用
- 右值引用和移动语义
- 完美转发
- 万能引用
- 完美转发
概念
左值和右值
- 左值
左值是一个可以被取地址、修改的对象或变量,其具有固定的内存地址。
左值可以出现在赋值语句的左边,因为它们表示一个可被修改的存储位置。
int a = 1; // a就是一个左值
- 右值
右值是那些不能被取地址、只能读取的数据。
右值通常包括常量、表达式结果、字符串字面量和临时变量(传值返回的函数返回值)等。
右值出现在赋值语句中的右边,计算得到某个数值或数据,再赋给左值。
int x = 1, y = 2; //1、2是右值
int a = x + y; //(x + y) 是右值
string name = "yyjs"; //"yyjs"是右值
左值引用和右值引用
C++11新特性 本质上都是进行取别名
- 左值引用
用&
符号表示,可以绑定到一个左值,因为它们都可取地址
int a = 1;
int &ref_a = a; // 左值引用
- 右值引用
用&&
符号表示,只能绑定到一个右值(临时变量、表达式、字面量等)
int &&rr1 = 1; // 右值引用
double&& rr2 = 1.2 + 2.9;
值得一提的是对于右值引用,如rr1、rr2。它们给右值取别名后,会分配特定的存储空间,通过右值引用,可以取地址,可以被修改。使用const修饰右值引用,禁止修改。
- const左值引用
左值引用不能引用右值,右值是临时的、不可修改的。不能将权限放大
const左值引用可以引用右值,保证数据不会修改
void show(const string& str)
{
cout << str << endl;
}
int main
{
show("12345");//使用右值传参
}
- move()
被move后的左值能够赋值给右值引用
int a = 1;
//int &&rr2 = a; error
int &&rr2 = std::move(a);
使用
左值引用
我们通常使用左值引用作为参数和返回值
void func1(const string& s){}
string& get(){}
都是为了减少(深)拷贝的消耗,提高效率
短板:但是当函数返回对象是一个局部变量 ,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。
string iTos(int value)
{
string str;
...
return str;
}
string s = iTos(123);
右值引用和移动语义
来解决左值引用的短板,提高效率
如果在string类中增加
移动构造函数
class string
{
public:
// s1.swap(s2)
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
// 移动构造
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
swap(s);
}
。。。
private:
char* _str;
size_t _size;
size_t _capacity; // 不包含最后做标识的\0
};
对于iTos()
的使用,调用到移动构造。
string s = iTos(123);
对于s的创建调用的是其构造函数。iTos()的返回值string对象是一个“将亡值”,即将消耗的对象。编译器会进行识别,并且调用string的移动构造函数string(string&& s){}
,函数内通过swap()将资源进行转移,从而避免深拷贝的消耗。
移动赋值
// 移动赋值
string& operator=(string&& s)
{
swap(s);
return *this;
}
string s;
s = iTos(123);//调用移动赋值
同理
move()
string s1("hello today~");
string s3 = std::move(s1);
同理,s1的资源通过右值引用,移动构造转移给了s3,并且s1为空。
完美转发
万能引用
在模板中,&&
符号代表万能引用,既能接收左值又能接收右值
template <class T>
void PrefectForward(T&& t) {}
右值被引用之后会被储存到特定的位置,而这个储存位置在函数参数中是可以被取地址和修改的,因此实际上已经退化为左值了
如果想在万能引用中保持右值属性,就需要完美转发
完美转发
要想在参数传递过程中保持其原有的属性,需要在传参时调用,forward()
函数
template<class T>
void PerfectForward(T&& t)
{
func(std::forward<T>(t));
}
完美转发在传参的过程中保留对象原生类型属性
使用示例:
template<class T>
struct ListNode
{
T _data;
ListNode* _next = nullptr;
ListNode* _prev = nullptr;
};
template<class T>
class list
{
typedef ListNode<T> node;
public:
。。。
// 右值引用版本
void push_back(T&& x)
{ //完美转发
insert(_head, std::forward<T>(x));
}
// 右值引用版本
void insert(node* pos, T&& x)
{
node* prev = pos->_prev;
node* newnode = new node;
//完美转发,同时会去调用T类的移动构造函数(如果有)
newnode->_data = std::forward<T>(x);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = pos;
pos->_prev = newnode;
}
private:
node* _head;
};
list<string> lt;
lt.push_back("world");//右值
🦀🦀观看~~