1. 左值引用、右值引用
右值: 只能在 = 右边使用的值
字面量、中间结果、临时对象/匿名对象
无法取地址,不能使用左值引用
左值: 可以在 = 左边使用的值
编程,观察现象:
#include <iostream>
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; // 10为右值,字面量
const char* s = "123edf";
// "abcd" = s; // "abcd"为右值,字面量
m = n + 2;
// n + 2 = m; // n + 2 为右值,中间结果
string str = string(s);
// string(s) = str; // string(s)为右值,临时对象
Print(m); // int n = m;
Print(10); // int n = 10;
Print2(m); // int& n = m;
// Print2(10); // int& n = 10; // 字面量(右值)不能初始化左值引用
Print3(m); // const int n = m;
Print3(10); // const int n = 10; // 补丁:const左值引用可以初始化左值和右值
// Print4(m); // int&& n = m; // 左值不能初始化右值引用
Print4(10); // int&& n = 10;
}
结果为:
12
10
12
12
10
10
- 10为字面量,为右值
- “abcd”为字面量,为右值
- n+2为中间结果,为右值
- string(s)为临时对象,为右值
- 普通函数传值,左值右值都可以传
- 10为右值,不能初始化左值引用
- 加const,右值也可以初始化左值引用,即左右都可
- m为左值,不能初始化右值引用
2. 移动构造函数、移动赋值运算符重载
2.1 不可拷贝对象的移动
对于不可拷贝对象,可以使用移动语法做拷贝构造和赋值。
移动的条件:
- 只有右值才能移动,如果左值要移动需要转换成右值
move( )
,或者使用匿名对象Uncopy()
- 对象的类要实现移动构造函数和移动赋值运算符重载
#include <iostream>
using namespace std;
class Uncopy{
public:
Uncopy() = default; // 构造函数,不做其他事
~Uncopy() = default; // 析构函数
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 v;
// return v;
return move(v);
}
// 移动条件:只有右值能移动
// 左值变右值:move( )
int main(){
Uncopy u;
// Uncopy w = u; // 拷贝构造
Uncopy w = Uncopy(); // 移动构造
Uncopy w2 = move(u); // 移动构造
Uncopy v;
// v = u; // 赋值运算符重载
v = Uncopy(); // 移动运算符重载
v = move(u); // 移动运算符重载
// Param(u); // Uncopy w = u; 拷贝构造
Param(Uncopy()); // Uncopy w = u;
Param(move(u)); // Uncopy w = u;
Return();
}
结果为:
[root@foundation1 C++7.17]# g++ right_value2.cpp -fno-elide-constructors
[root@foundation1 C++7.17]# ./a.out
rvalue copy constructor
rvalue copy constructor
rvalue assign
rvalue assign
rvalue copy constructor
rvalue copy constructor
rvalue copy constructor
2.2 可拷贝对象的移动
对于可拷贝对象,右值的移动不同于普通的赋值,右值的移动会让右值消失
移动的条件:
- 只有右值才能移动,如果左值要移动需要转换成右值(move())。
- 对象的类实现了移动构造函数和移动赋值运算符重载 。如果没有,执行拷贝操作。
#include <iostream>
#include <vector>
using namespace std;
class Simple{
public:
Simple(){cout << "constructor" << endl;}
Simple(const Simple&){cout << "copy constructor" << endl;} // 加const,左右值都能放
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); // 移动后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 = vec1; // 赋值运算符重载,数据还在
vec2 = move(vec1); // 移动后vec1数据会消失
cout << vec1.size() << "," << vec2.size() << endl;
// Simple a;
// Simple b = a;
// b = a;
// 在移动条件下,如果类内未定义移动函数,使用拷贝函数;定义了就使用移动函数
Simple a;
Simple b = move(a);
b = move(a);
}
结果为:
[root@foundation1 C++7.17]# g++ move.cpp -fno-elide-constructors
[root@foundation1 C++7.17]# ./a.out
0x7ffcc17343b0
0x7ffcc1734390 abcd
0,5
constructor
move constructor
move override
对于vector的移动:需要
vector<Simple> vs;
// vs.push_back(a); // 拷贝
vs.emplace_back(move(a)); // 移动
调用到移动构造函数
move constructor
vector动态内存是如何增长的?拷贝是如何完成的?
3. 万能引用、完美转发
万能引用: 用于模板参数
传入左值 T&& => T&
传入右值 T&& => T&&
但左右值传过来都是左值
完美转发:
forward<T>(param)
可以使param数据保持原来属性
该是左值就是左值,该是右值就是右值
#include <iostream>
#include <vector>
using namespace std;
class Simple{
public:
Simple(){cout << "constructor" << endl;}
Simple(const Simple&){cout << "copy constructor" << endl;} // 加const,左右值都能放
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;}
void Test() /*const*/ &{
cout << "lvalue Test" << endl;
}
void Test() &&{
cout << "rvalue Test" << endl;
}
};
// void Func(Simple s){} // 传值
void Func(Simple& s){cout << "lvalue" << endl;} // 传左值
void Func(Simple&& s){cout << "rvalue" << endl;} // 传右值
// void Func(const Simple& s){} // 传引用
// 模板
template<typename T>
void TemplateFunc(T&& param){ // 通用引用/万能引用
// Func(param); //左右值传过来都是左值
Func(forward<T>(param)); // 完美转发,保持原来左值右值的属性
}
int main(){
Simple s;
Func(s);
Func(move(s));
// 匿名对象
s.Test();
Simple().Test();
// 模板
TemplateFunc(s);
TemplateFunc(Simple());
}
结果为:
constructor
lvalue
rvalue
lvalue Test
constructor
rvalue Test
lvalue
constructor
rvalue