之前学习了C++的左值右值,但只留在了对于概念的理解上面。今天学习了一下移动构造函数,临时对象/将亡值即将被销毁时会唤起移动构造函数。还有一个std::move(),它的主要作用是将一个左值转换为右值引用,从而强制使用移动构造函数或移动赋值运算符。所以这个std::move()只是进行了一个类型转换而已,并不会触发移动操作。使用std::move()后,我们就可以调用接受右值引用的函数。
1 | template<typename T> |
再回顾一下左右值的定义:
左值:通常指的是有名字的变量或者持久对象,可以取它的地址。
右值:通常是临时对象,比如函数返回的临时对象、字面量(除了字符串字面量,它实际上是左值)等。
右值引用:使用两个&&表示,例如 int&&。右值引用可以绑定到右值,但不能绑定到左值。
我看有些教程说右值不能取地址,我一直对此非常疑惑:右值为什么不能取地址?既然存在,那肯定在内存/寄存器里,那么右值引用又是什么?
通过右值引用,我们可以延长右值的生命周期,右值引用本身是一个左值,因为它有名字。所以我们可以对右值引用取地址,这个地址就是被引用的右值对象的地址。
主要需要搞清楚两个问题:
- 右值引用是一个左值?是
- 右值引用是否在内存中创建了一个新对象,复制了即将销毁的右值?
不是的。右值引用并不创建新对象,它只是引用了那个右值(临时对象)。但是,由于右值引用本身是一个左值,所以当使用右值引用时,实际上是在直接操作那个临时对象。
1 | int main() { |
在这个例子中,用一个右值引用rref绑定了一个临时对象。根据C++规则,这样做会延长临时对象的生命周期,使其与右值引用的生命周期相同。所以,在main函数结束前,这个临时对象都不会被销毁。这里没有发生拷贝,也没有移动,只是给临时对象起了一个别名。
BUT
看看到现在,还是用高层的概念去解释一个新的概念。如果一直在高层徘徊,就会产生很多疑问:临时对象是在内存中存储吗?int&&a=2, 2的地址不能被取到,那为什么用右值引用就能取了?那是不是意味着编译器在中间就是开了一个临时对象呢?又有说表达式结束,临时对象就会被销毁。那么如果还是按照刚刚那些高层概念解释,对象都被销毁了,右值引用还在引用什么呢?
还是下到汇编看看。终于在知乎找到了一篇文章,终于在汇编层面解答了我上面的问题:
https://zhuanlan.zhihu.com/p/389978619?share_code=IPKeoBHAwX3u&utm_psn=1960118829079307210
下面是答案:
int &&a=0;
其中a是一个右值引用。
这个汇编代码等价于一个左值引用(底层看就是指针常量)引用了一个匿名变量,此匿名变量在C++中不可见,但其实该变量存在于栈上。总结来说,右值引用的底层就是一个指针指向了一个匿名变量。那么如果对右值引用重新赋值修改,改的就是匿名变量的值。