右值引用是c++11提出的, 还真难搞懂, 花了不少的时间找资料
背景
先需要知道什么是左值什么是右值
简单的来说 左值 出现在等号的左边和右边
右值 只能出现在等号的右边, 不能出现在等号的左边
简单的来说 左值 出现在等号的左边和右边
右值 只能出现在等号的右边, 不能出现在等号的左边
令一种区分左值和右值的办法是取地址符, 只有左值可以被取地址, 右值无法被取地址
所以, 右值是一些常量和一些临时变量, 没有名字
左值是一些有名字的变量
左值是一些有名字的变量
move()
move()
的作用将右值利用起来, 不被浪费比如一个函数返回了一个占用内存非常大的变量, 在c++中这样势必会出现拷贝的开销
比如
1 2 3 4 5 6 7 8 | struct st { int arr[10000]; } st foo() { st s; return s; } |
如果运行
假如想避免这种情况, 可以考虑用指针,
st s = foo();
势必会拷贝一份给s, 再将临时变量删除假如想避免这种情况, 可以考虑用指针,
const st &
这样的返回值, 这又导致了动态分配内存, 大家都不想动不动就动态分配内存吧, 手动释放内存需要考虑太多东西了
所以, 你看可不可以延长临走变量的生命周期呢, 直接将变量
s
直接指向临时变量的内存呢, 即函数foo()
中的s
的那片内存
嗯, 肯定可以啊, 这就是
所以, 原来的函数可以这么写
move()
的作用所在所以, 原来的函数可以这么写
1 2 3 4 | st &&foo() { st s; return std::move(s); } |
然后可以运行
st &&s = foo();
这样就可以完美解决
又比如
1 2 3 4 5 6 7 | std::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 7 | void 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 &)