右值引用是c++11提出的, 还真难搞懂, 花了不少的时间找资料
背景
先需要知道什么是左值什么是右值
简单的来说 左值 出现在等号的左边和右边
右值 只能出现在等号的右边, 不能出现在等号的左边
令一种区分左值和右值的办法是取地址符, 只有左值可以被取地址, 右值无法被取地址
所以, 右值是一些常量和一些临时变量, 没有名字
左值是一些有名字的变量
要想要搞懂右值引用就要明白右值引用的出现是来解决什么问题的
可以看这里 https://www.zhihu.com/question/22111546
里面讲的非常明白, 关于右值引用出现的缘由
move()
move()
的作用将右值利用起来, 不被浪费
比如一个函数返回了一个占用内存非常大的变量, 在c++中这样势必会出现拷贝的开销
比如1
2
3
4
5
6
7
8struct st {
int arr[10000];
}
st foo() {
st s;
return s;
}
如果运行st s = foo();
势必会拷贝一份给s, 再将临时变量删除
假如想避免这种情况, 可以考虑用指针, const st &
这样的返回值, 这又导致了动态分配内存, 大家都不想动不动就动态分配内存吧, 手动释放内存需要考虑太多东西了
所以, 你看可不可以延长临走变量的生命周期呢, 直接将变量s
直接指向临时变量的内存呢, 即函数foo()
中的s
的那片内存
嗯, 肯定可以啊, 这就是move()
的作用所在
所以, 原来的函数可以这么写1
2
3
4st &&foo() {
st s;
return std::move(s);
}
然后可以运行st &&s = foo();
这样就可以完美解决
又比如1
2
3
4
5
6
7std::vector<int> tmp(10000);
std::vector<std::vector<int>> v;
v.push_back(tmp);
// 接下来变量tmp就不用了
在这样的情况下, tmp拷贝到v中是不是又感觉有点浪费呢, 因为tmp不再使用, 所以可以写成v.push_back(std::move(tmp));
这样又节省了复制的开销
forward()
这个又是干啥的呢, 一开始接触的我一脸懵逼.
总的来说, forward为了区分一个参数被传进来, 他到底是左值还是右值, 然后去调用相应的函数
比如1
2
3
4
5
6
7void f(int &i) { }
void f(int &&i) { }
template<class T>
void foo(T &&t) {
f(t);
}
这样的代码, 我调用函数foo()
去传进去一个右值, 到了函数foo()
中, 参数t
就是左值, 因为t
是有名字的, 所以不管这么调用f(t)
都会去调用 f(int &i)
这个函数
所以这个时候需要去区分左值还是右值, 可以这么写 f(std::forward<T>(t))
, forward
会自动转发到相应的函数
关于上面的代码, 你可能会有个疑问就是 foo(T &&t)
为什么可以传左值?
嗯, 就这个问题, 我也懵逼了, 不过查了半边资料, 加上在stackoverflow
上提问, 终于终于找到了答案
对于C++语言,不可以在源程序中直接对引用类型再施加引用。T& &将编译报错。C++11标准中仍然禁止上述显式对引用类型再施加引用,但如果在上下文环境中(包括模板实例化、typedef、auto类型推断等)如出现了对引用类型再施加引用,则施行引用塌缩规则(reference collapsing rule)[注 10]:
T& &变为T&
T& &&变为T&
T&& &变为T&
T&& &&变为T&&
我就说我将T
换成具体的变量类型就不行了
所以, 当foo(T &&t)
被传入左值的时候, 比如 1, T
就变成了 int &
, 所以foo(T &&t)
就变成了foo(int & &&)
, 根据上面的规则, 就变成了foo(int &)