一篇讲的很好的博客:https://www.jianshu.com/p/d19fc8447eaa
左值 右值:
看能不能对表达式取地址,如果能,则为左值,否则为右值。
将亡值和纯右值:
左值 右值:
纯右值:临时变量,.rodata
将亡值:std::move()将一个左值转换为右值, T&&函数返回值
看能不能对表达式取地址,如果能,则为左值,否则为右值。
将亡值和纯右值:
移动构造函数
class D
{
private:
int val1;
int val2;
public:
D(int x= 0, int y=0): val1(x), val2(y){ cout << "D construct" << endl;}
D(const D& d) { cout << "D copy construct" << endl; }
D(const D&& d) { cout << "D move construct" << endl; }
};
int main()
{
D d;
D d1(move(d));
return 0;
}
结果:
D construct
D move construct
如果不用move,那么默认的是调用拷贝构造函数
假设有下面的代码
vector<string> vec;
for (int i = 0; i < 1000; i++)
{
vec.push_back(string("123")); // 调用构造函数和拷贝构造函数
}
改成
vector<string> vec;
for (int i = 0; i < 1000; i++)
vec.push_back(move(string("123")));
调用的是移动构造函数
3.左值引用 右值引用 常量左值引用
左值引用, 使用 T&, 只能绑定左值
右值引用, 使用 T&&, 只能绑定右值
常量左值, 使用 const T&, 既可以绑定左值又可以绑定右值
已命名的右值引用,编译器会认为是个左值
4.高性能的swap(假设T可移动)
template <typename T>
void swap(T& a, T& b)
{
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
5.通用引用
universal references(通用引用)
template<class T>
void fun(T&& x)
{
}
fun(1); // 右值
int x = 1;
fun(x); // 左值
是左值引用还是右值引用却决于它的初始化
universal references仅仅发生在 T&& 下面,任何一点附加条件都会使之失效
实际上是左值的右值引用:
class D
{
private:
int val1;
int val2;
public:
D(int x= 0, int y=0): val1(x), val2(y){}
void print() { cout << "helloworld" << endl; }
};
D getD()
{
return D();
}
int main()
{
D&& d = getD();
d.print();
return 0;
}
完美转发:
原参数可能是右值,可能是左值,如果还能继续保持参数的原有特征,那么它就是完美的。
void process(int& i){
cout << "process(int&):" << i << endl;
}
void process(int&& i){
cout << "process(int&&):" << i << endl;
}
void myforward(int&& i){
cout << "myforward(int&&):" << i << endl;
process(i);
}
int main()
{
int a = 0;
process(a); //a被视为左值 process(int&):0
process(1); //1被视为右值 process(int&&):1
process(move(a)); //强制将a由左值改为右值 process(int&&):0
myforward(2); //右值经过forward函数转交给process函数,却称为了一个左值,
//原因是该右值有了名字 所以是 process(int&):2
myforward(move(a)); // 同上,在转发的时候右值变成了左值 process(int&):0
// forward(a) // 错误用法,右值引用不接受左值
}
利用通用引用和forward可以实现完美转发
void RunCode(int &&m) {
cout << "rvalue ref" << endl;
}
void RunCode(int &m) {
cout << "lvalue ref" << endl;
}
void RunCode(const int &&m) {
cout << "const rvalue ref" << endl;
}
void RunCode(const int &m) {
cout << "const lvalue ref" << endl;
}
template<class T>
void pf(T&& a)
{
RunCode(forward<T>(a));
}
int main()
{
int a = 0;
int b = 0;
const int c = 0;
const int d = 0;
pf(a); // lvalue ref
pf(move(b)); // rvalue ref
pf(c); // const lvalue ref
pf(move(d)); // const rvalue ref
return 0;
}
总结:
由两种值类型,左值和右值。
有三种引用类型,左值引用、右值引用和通用引用。
左值引用只能绑定左值,右值引用只能绑定右值,通用引用由初始化时绑定的值的类型确定。
左值和右值是独立于他们的类型的,右值引用可能是左值可能是右值,
如果这个右值引用已经被命名了,他就是左值。
引用折叠规则:所有的右值引用叠加到右值引用上仍然是一个右值引用,
其他引用折叠都为左值引用。当T&&为模板参数时,输入左值,
它将变成左值引用,输入右值则变成具名的右值应用。
移动语义可以减少无谓的内存拷贝,要想实现移动语义,
需要实现移动构造函数和移动赋值函数。
std::move()将一个左值转换成一个右值,强制使用移动拷贝和赋值函数,
这个函数本身并没有对这个左值什么特殊操作。
std::forward()和universal references通用引用共同实现完美转发。
用empalce_back()替换push_back()增加性能。