如标题。最近在看现代 cpp ,感觉右值和右值引用这两个概念非常重要,因此自己尝试构建一些例子来加深理解。例子如下:
#include <iostream>
#include <ostream>
#include <utility>
using namespace std;
struct C {
int a = 1;
C() { cout << "Ha ha" << endl; }
C(C& c) : a(c.a) { cout << "Copy" << endl; }
C(C&& c) : a(std::move(c.a)) { cout << "Move" << endl; }
~C() { cout << "Fucked" << endl; }
};
C func() {
C shit;
cout << &shit << endl;
return shit;
}
C f2() {
C&& shit = func();
cout << &shit << endl;
return shit;
}
C f3() {
C&& shit = func();
cout << &shit << endl;
return std::move(shit);
}
int main() {
auto&& shit = f2();
// Ha ha
// 0x5ffe24
// 0x5ffe24
// Copy
// Fucked
cout << &shit << endl;
// 0x5ffe7c
cout << "*************" << endl;
auto shit2 = f3();
cout << &shit2 << endl;
// Ha ha
// 0x5ffe24
// 0x5ffe24
// Move
// Fucked
// 0x5ffe78
cout << "*************" << endl;
auto&& shit3 = f3();
cout << &shit3 << endl;
// Ha ha
// 0x5ffe24
// 0x5ffe24
// Move
// Fucked
// 0x5ffe74
cout << "*************" << endl;
// cout << shit.a;
}
对于这个例子所产生的结果,我不是很懂,我主要是不太懂以下几个问题:
1
lzoje 40 天前 1
先回答下第三点,这实际上应该是编译器 nrvo 优化的效果( n 是 named value ,rvo 是 return value optimization )。这个优化的效果相当于给被优化的函数添加一个引用参数,这个引用就是返回的引用。
所以第一点的问题,f2 和 f3 里都无法做 nrvo 优化,因为返回值是一个右值引用,而不是栈变量(虽然我们能看到那个是栈变量,但是编译器应该是判断不到)。 第二点很简单,你返回栈地址就是有问题的,虽然编译器可以给你做这个优化,但不是所有地方都能做这个优化,这得编译器决定。我个人理解是这样的。 |
2
sanbuks 40 天前 1
1. 这边结果是 move ,而不是 copy ,建议再试一下
2. 会产生悬垂引用问题, 一般直接返回 T 即可,返回右值引用情况很少见,比如这样 T{}.func(); 3. 本质就是直接构造到最终目标的存储中,具体参考不同编译器优化 |
3
zizon 40 天前 1
func -> 没有 rvalue 之前 return 会 copy,rvalue 的引入就是为了解决这种不必要的 copy.
f2 -> 类似 func,看编译器有没激进到断定 shit 可以以 rvalue 做 rvo, 不能的话就会保守 copy.因为 shit 本身指向一个 rvalue,所以编译器有理由认为它可能在 return 后不是一个有效地址,就是你 2 里提到的 clang 警告的原因.如果编译器先把 func inline 了,那么就可能出现 2L 说的第一点的情况. f3 -> 类似 f2,std::move 产生了一个 rvalue.调用 move ctor 是编译器认为这个 rvalue 可能不是有效的地址.同 f2,编译器也可以激进优化成 func. 通俗的理解 ravlue 就是之前没有 rvalue 的时候会产生隐式不可代码层面 reference 的 copy ctor 对象的一个称呼. 有了这个定义之后,新标准就可以有规则 eliminate 这种不必要的 copy ctor. |
4
Symbo1ic OP |
5
lzoje 40 天前
@Symbo1ic 现在的编译器应该都默认启动返回值优化的,如果没有优化会有很多次复制的。需不需要返回一个 move 包裹的值,是看具体情况的,毕竟移动有什么效果取决于你的移动构造或者移动赋值函数做了什么。
|
6
lzoje 40 天前
对了,第一个问题我前面看错了。为什么 shift 会 copy ,shift2 会 move 。因为你返回的都不是引用类型呀。你要知道,main 和 f2 和 f3 的栈空间都是不一样的。f2 f3 结束后,在其栈上的东西对于 main 来说都是不可用的,所以最后返回的东西要复制到 main 的栈上,所以会有 copy 或者 move 。如果你返回引用就不会了。
|