Thursday, December 27, 2018

c++右值引用




右值引用是c++11提出的, 还真难搞懂, 花了不少的时间找资料

背景

先需要知道什么是左值什么是右值
简单的来说 左值 出现在等号的左边和右边
右值 只能出现在等号的右边, 不能出现在等号的左边

令一种区分左值和右值的办法是取地址符, 只有左值可以被取地址, 右值无法被取地址
所以, 右值是一些常量和一些临时变量, 没有名字
左值是一些有名字的变量
要想要搞懂右值引用就要明白右值引用的出现是来解决什么问题的
可以看这里 https://www.zhihu.com/question/22111546
里面讲的非常明白, 关于右值引用出现的缘由

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 &)

0 comments:

Post a Comment