提问人:CPW 提问时间:11/7/2023 更新时间:11/7/2023 访问量:133
为什么 std::move 可以移动堆栈数据对象?
Why std::move can move a stack data object?
问:
我对以下关于移动堆栈对象的代码感到困惑:
int *pOuter = nullptr;
{
int j;
std::cout << "top of the inner stack=" << &j << std::endl;
int aData[] = {1,2,3,4,5}; // in a vanishing stack
aData[0] = 0; // to make sure that aData is on stack
int *pInner = aData;
std::cout << "Before Move: pOuter=" << pOuter << ", pInner=" << pInner << std::endl;
pOuter = std::move(pInner);
std::cout << "After Move: pOuter=" << pOuter << ", pInner=" << pInner << std::endl;
}
// aData[0] and pInner are no longer accessible
int i;
std::cout << "top of the outer stack=" << &i << std::endl;
int aCover[] = {10,9,8,7,6,5,4,3,2,1}; // to reclaim stack
std::cout << "aCover=" << aCover << ", pOuter=" << pOuter << ": ";
for (i = 0; i < 5; ++i) {
std::cout << *(pOuter + i) << ",";
}
std::cout << std::endl;
输出为:
top of the inner stack=0xb2125ff7cc
Before Move: pOuter=0, pInner=0xb2125ff7b0
After Move: pOuter=0xb2125ff7b0, pInner=0xb2125ff7b0
top of the outer stack=0xb2125ff804
aCover=0xb2125ff7d0, pOuter=0xb2125ff7b0: 0,2,3,4,5,
我很惊讶它为什么会起作用,因为堆栈对象 sData 应该被 aCover 销毁并覆盖,这样 pOuter 应该会导致分段失败,或者至少指向 sCover。相反,aData 不会与内部堆栈一起销毁。
从显示的地址来看,我的猜测是 C++ 编译器实际上在内部堆栈之前分配了以下堆栈对象:
int i;
int aCover[] = {10,9,8,7,6,5,4,3,2,1};
即便如此,在我看来,pOuter 是一个悬而未决的参考,它的工作是偶然的。
我说得对吗?
答:
3赞
Paul Sanders
11/7/2023
#1
正如评论中所指出的,只是一个类型转换。那么它是如何工作的呢?std::move
好吧,让我们以下面的代码作为一个简单的例子:
Foo x;
Foo y = x;
这制作了 in 的副本,即它调用 的 copy 构造函数来复制 。x
y
y
x
鉴于这:
Foo x;
Foo y = std::move (x);
进入 ,如果您不再关心,则效率更高。x
y
x
但是 - 这是关键 - 只有在有合适的移动构造函数的情况下才做任何有用的事情,其签名将是:std::move
Foo
Foo &Foo (Foo &&other)
即参数必须是 ,并且本质上是强制转换为右值(以左值开始),从而告诉编译器调用移动构造函数而不是复制构造函数。other
rvalue
std::move
x
仅此而已。它本身不会移动任何东西。怎么可能?它不知道该怎么做,如果没有合适的移动构造函数存在,编译器将回退到复制。特别是基元类型的情况。std::move
x
评论
0赞
CPW
11/7/2023
在 C++11 及更高版本中,可以移动返回或函数参数列表中的 temp 对象(所谓的纯右值)。这可能是通过调整堆栈指针来完成的,也可能是通过调整参数列表的推送顺序来完成的,所以我猜它不仅仅是一个简单的强制转换。对于我创建的案例,我非常惊讶堆栈以这样一种方式重新排列,使移动成为可能。我想知道编译器是否会查看 std::move() 目标并进行此类调整。
0赞
CPW
11/7/2023
在 C++11 及更高版本中,以下代码不会生成副本: A f() { return A();A a = f();原因似乎是当 f() 退出时,堆栈指针不会倒带,因此可以将 f() 中的数据对象 A 分配给 a。所以它似乎不是一个纯粹的类型演员。
1赞
xaxxon
11/7/2023
根据您在上面评论中对术语的使用,您仍然不了解 std::move 的作用。这只是一个演员表。就是这样。它对堆栈或堆一无所知,堆栈或堆也与它无关。
1赞
Paul Sanders
11/7/2023
@CPW 你的这些评论离谱太远了。只要假装(因为这是真的)在你发布的代码中根本没有做任何事情,也许然后你会看到它。std::move
1赞
xaxxon
11/7/2023
在你意识到“std::move不会移动任何东西”之前,你需要继续阅读并停止打字,因为你只会让自己更加困惑。std::move 不动。
评论
pOuter
std::move
只是一个类型转换,仅此而已。对于基元类型(如指针),移动与复制相同,因此与将外部指针设置为指向超出范围的本地数据相同,从而使外部指针悬空。pOuter = std::move(pInner);
pOuter = pInner;
std::move
并没有真正移动任何东西。这是一个演员表。移动赋值运算符和移动构造函数确实会移动,但普通指针缺少其中任何一个,因此代码中不会发生任何移动。std::move
可以移动堆栈数据对象?它不能。事实并非如此。std::move