10 C++11左值右值、左值引用、右值引用
10.1 左值&右值
左值:可以在=左边使用的值
右值:只能在=右边使用的值(字面量、中间结果、临时对象/匿名对象),无法直接取地址,不能使用左值引用。
10.2 左值引用&右值引用
实例:左值引用、右值引用
#include <iostream>
#include <string>
using namespace std;
void Print(int n){
cout << n << endl;
}
void Print2(int& n){
cout << n << endl;
}
void Print3(const int& n){
cout << n << endl;
}
void Print4(int&& n){ //右值引用
cout << n << endl;
}
int main(){
//右值:只能在=右边使用的值(字面量\中间结果\临时对象或匿名对象),无法取地址,不能左值引用
//左值:可以在=左边使用的值
int n = 10;
int m = n;
//10 = n; //字面量不能左值引用
//n+2 = m; //中间结果不能左值引用
const char* s = "123abc";
//"abcd" = s;
string str = s;
//string(s) = str; 临时对象不能左值引用
Print(m);
Print(10);
Print2(m); //int& n = m;
//Print2(10); //int& n = 10; 常量不能被引用
//int& f = 10; //右值不能初始化左值引用
Print3(m); //const int& n = m;
Print3(10); //const int& n = 10; 补丁:const左值引用可以初始化左值和右值
//Print4(m); int&& n = m; 左值不能初始化右值引用
Print4(10);
}
[root@localhost 9]# ./a.out
10
10
10
10
10
10
结论:
1、左值引用只能用左值初始化
2、右值引用只能用右值初始化
3、const
左值引用可以初始化左值和右值
10.3 移动构造函数&移动赋值运算符重载
10.3.1 不可复制对象的移动
实例:
#include <iostream>
using namespace std;
class Uncopy{
public:
Uncopy(){cout << "constructor" << endl;}
~Uncopy(){cout << "destructor" << endl;}
Uncopy(const Uncopy&) = delete; //禁用赋值
Uncopy& operator=(const Uncopy&) = delete; //禁用拷贝构造
Uncopy(Uncopy&&){cout << "rvalue copy constructor" << endl;}
Uncopy& operator=(Uncopy&&){cout << "rvalue assign" << endl; return *this;}
};
void Param(Uncopy w){}
Uncopy Return(){
Uncopy u;
return u;
}
// 移动的条件:只有右值可以移动
// 左值变右值:move()
int main(){
Uncopy u;
//Uncopy v = u; //拷贝构造
Uncopy w = Uncopy();
Uncopy v;
v = move(u); //赋值运算符重载
v = Uncopy(); //移动运算符重载
//Param(Uncopy()); //Uncopy w = Uncopy();
Param(move(u)); //Uncopy w = u;
Return();
}
constructor
constructor
rvalue copy constructor
destructor
constructor
rvalue assign
constructor
rvalue assign
destructor
rvalue copy constructor
destructor
constructor
rvalue copy constructor
destructor
destructor
destructor
destructor
destructor
不可复制对象,可以使用移动语法做拷贝构造和赋值。
- 移动的条件:
1、只有右值才能移动,如果左值要移动需要转换成右值(move()
)。
2、对象的类要实现移动构造函数和移动赋值运算符重载。
10.3.2 可复制对象的移动
实例:
#include <vector>
using namespace std;
//可复制对象使用移动
class Simple{
public:
Simple(){cout << "constructor" << endl;}
Simple(const Simple&){cout << "copy constructor" << endl;}
Simple& operator=(const Simple&){cout << "assign constructor" << endl; return *this;}
Simple (Simple&&){cout << "move constructor" << endl;}
Simple& operator=(Simple&&){cout << "move override" << endl; return *this;}
~Simple(){cout << "destructor" << endl;}
//加const只能放左值,不加const既能放左值,也能放右值
};
int main(){
string s = "abc";
//string t = s;
string t = move(s);
cout << (void*)s.c_str() << ":" << s << endl; //s执行move之后内容为空
cout << (void*)t.c_str() << ":" << t << endl;
vector<int> vec = {1,2,3};
vector<int> vec2;
vec2 = move(vec);
cout << vec.size() << " " << vec2.size() << endl; //vec被move之后内容为空,大小为0
//移动条件下,如果类内未定义移动函数,使用拷贝函数,定义了则使用移动函数
Simple s1;
Simple s2 = move(s1); //移动构造
s2 = move(s1); //移动重载
//STL中的移动
vector<Simple> vs;
//vs.push_back(s1); //拷贝构造
vs.emplace_back(move(s1)); //移动构造
}
0x7fff2bf29880:
0x7fff2bf29860:abc
0 3
constructor
move constructor
move override
move constructor
destructor
destructor
destructor
- 移动的条件:
1、只有右值才能移动,如果左值要移动需要转换成右值(move()
)。
2、对象的类实现了移动构造函数和移动赋值运算符重载 。否则,执行拷贝操作。
10.3.3 STL中的移动
实例:
#include <iostream>
#include <vector>
using namespace std;
// 可复制对象使用移动
class Simple{
public:
Simple(){cout<< "constructor" << endl;}
Simple(const Simple&){cout<< "copy constructor" << endl;}
Simple& operator=(const Simple&){cout<< "assign override" << endl; return *this;}
Simple(Simple&&){cout<< "move constructor" << endl;}
Simple& operator=(Simple&&){cout<< "move override" << endl; return *this;}
};
int main(){
string s = "abcd";
// string t = s;
string t = move(s);
cout << (void*)s.c_str() << " " << s << endl;
cout << (void*)t.c_str() << " " << t << endl;
vector<int> vec1 = {1,2,3,4,5};
vector<int> vec2;
vec2 = move(vec1);
cout << vec1.size() << "," << vec2.size() << endl;
vector<Simple> vs;
Simple simple;
//vs.push_back(simple); // 拷贝构造
//vs.push_back(simple); // 拷贝构造
//vs.push_back(simple); // 拷贝构造
//vs.push_back(simple); // 拷贝构造
vs.emplace_back(move(simple)); // 移动构造
vs.emplace_back(move(simple)); // 移动构造
vs.emplace_back(move(simple)); // 移动构造
}
0x7ffc13aebf70
0x7ffc13aebf50 abcd
0,5
constructor
move constructor
move constructor
copy constructor
move constructor
copy constructor
copy constructor
C++11 string
和容器都实现移动构造函数和移动赋值运算符重载。
C++11 STL
容器提供了移动操作的成员函数emplace_*()
。
结果分析:
这里vs
在move
第一个simple
对象时候申请1
个空间并放入vs[0]
。move
第二个simple
时候,先申请2
个空间,move
第二个simple
放入vs[1]
,再把第一个simple
拷贝到vs[0]
的位置。同理,move
第三个simple
时候,申请4个空间,move
第三个simple
到vs[2]
,然后拷贝前两个simple
分别放入vs[0]
和vs[1]
中。
注:vector
每次申请1、2、4、8…个空间
10.4 万能引用&完美转发
实例:
#include <iostream>
#include <vector>
using namespace std;
//可复制对象使用移动
class Simple{
public:
Simple(){cout << "constructor" << endl;}
Simple(const Simple&){cout << "copy constructor" << endl;}
Simple& operator=(const Simple&){cout << "assign override" << endl; return *this;}
Simple (Simple&&){cout << "move constructor" << endl;}
Simple& operator=(Simple&&){cout << "move override" << endl; return *this;}
//左值调用左值Test
void Test()&{ //void Test(Simple*& this)
cout << "lvalue Test" << endl;
}
//右值调用右值Test
void Test()&&{ //void Test(Simple*&& this)
cout << "rvalue Test" << endl;
}
};
void Func(Simple& s){cout << "lvalue" << endl;}
void Func(Simple&& s){cout << "rvalue" << endl;}
template<typename T>
//模板参数&&:万能引用/通用引用,根据传入参数左右值类型不同,调用不同参数类型的函数
void TemplateFunc(T&& param){
//Func(param);
//完美转发
Func(forward<T>(param));
forward<T>(param).Test();
}
int main(){
Simple s;
Func(s); //左值
Func(move(s)); //右值
s.Test(); //调用左值Test
Simple().Test(); //调用右值Test
TemplateFunc(s); //传入左值 T&& => T& void TemplateFunc (Simple& param)
TemplateFunc(Simple()); //传入右值 T&& ==> T&& void TemplateFunc(Simple&& param)
}
[root@localhost 9]# ./a.out
constructor
lvalue
rvalue
lvalue Test
constructor
rvalue Test
lvalue
lvalue Test
constructor
rvalue
rvalue Test
1、函数参数中的&&
表示右值引用,函数模板参数中的&&
表示万能引用。通用引用/万能引用,根据参数左右值类型的不同,生成不同参数类型的函数。
2、完美转发 forward<T>()
根据万能引用参数的不同(左值引用还是右值引用),恢复参数的状态(左值引用保持左值,右值引用转成右值),实现函数调用的转发(传入左值,调用左值引用的函数;传入右值,调用右值引用的函数)。