文章目录
前言
C++98中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通
过指针来实现的,因此使用引用,可以提高程序的可读性。
一、右值引用是什么?
为了提高程序运行效率,C++11中引入了右值引用,右值引用也是别名,但其只能对右值引用。
C++98就提出了引用的概念,引用就给一个对象取别名。
C++98 左值引用
C++11 右值引用
不管是左值引用,还是右值引用,他们都是给对象取别名,
不过左值引用主要是给左值取别名,右值引用主要是给右值取别名。
什么是左值?什么是右值?
// =左边就是左值?右边的就是右值? 注意这个是一个C语法就留下的坑,就像左移和右移一样,
这里左右不是方向。左边的值也不一定左值,右边的值不一定是右值
int x1 = 10; int x2 = x1; int x3 = x1+x2; 这里x1是左值,10是右值,x2是左值,x1+x2表达式返回值就是右值
可以修改就可以认为是左值,左值通常是变量
右值通常是常量,表达式或者函数返回值(临时对象)
int main()
{
int x = 1, y = 2;
// 左值引用的定义
int a = 0;
int& b = a;
// 左值引用不能引用右值, const左值引用可以
//int& e = 10;
//int& f = x + y;
const int& e = 10;
const int& f = x + y;
// 右值引用的定义
int&& c = 10;
int&& d = x + y;
// 右值引用不能引用左值,但是可以引用move后左值
//int&& m = a;
int&& m = move(a);
return 0;
}
template<class T>
void f(const T& a)
{
cout << "void f(const T& a)" << endl;
}
template<class T>
void f(const T&& a)
{
cout << "void f(const T&& a)" << endl;
}
int main()
{
int x = 10;
f(x); // 这里会匹配左值引用参数的f
f(10);// 这里会匹配右值引用参数的f
return 0;
}
二、右值区分与应用?
C++11又将右值区分为:纯右值和将亡值
纯右值:基本类型的常量或者临时对象
将亡值:自定义类型的临时对象
结论:所有深拷贝类(vector/list/map/set…),都可以加两个右值引用做参数的移动拷贝和移动赋值
1.应用:右值引用的移动构造和移动赋值,可以减少拷贝
class String
{
public:
String(const char* str = "")
{
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
// s2(s1)
String(const String& s)
{
cout << "String(const String& s)-深拷贝-效率低" << endl;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
// s3(右值-将亡值)
String(String&& s)
:_str(nullptr)
{
// 传过来的是一个将亡值,反正你都要亡了,我的目的是跟你有一样大的空间,一样的值
// 不如把你的控制和只给我
cout << "String(String&& s)-移动拷贝-效率高" << endl;
swap(_str, s._str);
}
String& operator=(const String& s)
{
return *this;
}
String& operator=(String&& s)
{
return *this;
}
~String()
{
delete[] _str;
}
private:
char* _str;
};
String f(const char* str)
{
String tmp(str);
return tmp; // 这里返回实际是拷贝tmp的临时对象
}
int main()
{
String s1("左值");
String s2(s1); // 参数是左值
String s3(f("右值-将亡值")); // 参数是右值-将亡值(传递给你用,用完我就析构了)
return 0;
}
2.应用:当传值返回值,返回是右值,结合前面学的移动构造和移动赋值.
3.应用:右值引用去做函数的参数,减少拷贝
//std::vector::push_back
//void push_back(const value_type& val);
//void push_back(value_type&& val);
//
//std::list::push_back
//void push_back(const value_type& val);
//void push_back(value_type&& val);
//
//std::set::insert
//pair<iterator, bool> insert(const value_type& val);
//pair<iterator, bool> insert(value_type&& val);
int main()
{
vector<string> v;
string s1("左值");
string s2("左值");
int val = 1234;
// push_back中调用的是string的拷贝构造
v.push_back(move(s2)); // void push_back(const value_type& val);
// push_back中调用的是string的移动构造
v.push_back("右值"); //void push_back(value_type&& val);
v.push_back(to_string(val)); // void push_back(value_type&& val);
v.emplace_back(s1); // v.emplace_back(move(s1));
v.emplace_back("右值");
vector<pair<string, string>> vp;
vp.push_back(make_pair("右值", "右值"));
pair<string, string> kv("左值", "左值");
vp.push_back(kv);
vp.emplace_back(make_pair("右值", "右值"));
vp.emplace_back(kv);
vp.emplace_back("右值", "右值"); // 体现emplace_back模板可变参数特点的地方
return 0;
}
三、完美转发
右值引用会第二次之后的参数传递过程中右值属性丢失,下一层调用会全部识别为左值
完美转发解决
1.没使用完美转发
void Fun(int &x){ cout << "lvalue ref" << endl; }
void Fun(const int &x){ cout << "const lvalue ref" << endl; }
void Fun(int &&x){ cout << "rvalue ref" << endl; }
void Fun(const int&& x){ cout << "const rvalue ref" << endl; }
template<typename T>
void PerfectForward(T &&t)
{
Fun(t);
}
int main()
{
PerfectForward(10); // rvalue ref
int a;
PerfectForward(a); // lvalue ref
PerfectForward(std::move(a)); // rvalue ref
const int b = 8;
PerfectForward(b); // const lvalue ref
PerfectForward(std::move(b)); // const rvalue ref
return 0;
}
可以看出全部都是左值
2.使用forward后
void Fun(int &x){ cout << "lvalue ref" << endl; }
void Fun(const int &x){ cout << "const lvalue ref" << endl; }
void Fun(int &&x){ cout << "rvalue ref" << endl; }
void Fun(const int&& x){ cout << "const rvalue ref" << endl; }
template<typename T>
void PerfectForward(T &&t)
{
Fun(std::forward<T>(t));
}
int main()
{
PerfectForward(10); // rvalue ref
int a;
PerfectForward(a); // lvalue ref
PerfectForward(std::move(a)); // rvalue ref
const int b = 8;
PerfectForward(b); // const lvalue ref
PerfectForward(std::move(b)); // const rvalue ref
return 0;
}
总结
右值引用做参数和作返回值减少拷贝的本质是利用了移动构造和移动赋值
左值引用和右值引用本质的作用都是减少拷贝,右值引用本质可以认为是弥补左值引用不足的地方, 他们两相辅相成
左值引用:解决的是传参过程中和返回值过程中的拷贝
做参数:void push(T x) -> void push(const T& x) 解决的是传参过程中减少拷贝
做返回值:T f2() -> T& f2() 解决的返回值过程中的拷贝
ps:但是要注意这里有限制,如果返回对象出了作用域不在了就不能用传引用, 这个左值引用无法解决,等待C++11右值引用解决
右值引用:解决的是传参后,push/insert函数内部将对象移动到容器空间上的问题.+传值返回接收返回值的拷贝
做参数: void push(T&& x) 解决的push内部不再使用拷贝构造x到容器空间上,而是移动构造过去
做返回值:T f2(); 解决的外面调用接收f2()返回对象的拷贝,T ret = f2(),这里就是右值引用的移动构造,减少了拷贝