首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >是否可以移动本地堆栈变量?

是否可以移动本地堆栈变量?
EN

Stack Overflow用户
提问于 2017-03-28 13:12:47
回答 2查看 26.6K关注 0票数 45

请考虑以下代码:

代码语言:javascript
复制
struct MyStruct
{
    int iInteger;
    string strString;
};

void MyFunc(vector<MyStruct>& vecStructs)
{
    MyStruct NewStruct = { 8, "Hello" };
    vecStructs.push_back(std::move(NewStruct));
}

int main()
{
    vector<MyStruct> vecStructs;
    MyFunc(vecStructs);
}

为什么要这么做?

在调用MyFunc时,返回地址应该放在当前线程的堆栈上。现在创建NewStruct对象,这个对象也应该放在堆栈上。使用std::move,我告诉编译器,我不再打算使用NewStruct引用了。他能偷取记忆。( push_back函数是具有移动语义的函数。)

但是当函数返回时,当NewStruct超出作用域时。即使编译器不会从堆栈中删除原来由现有结构占用的内存,他也至少要删除先前存储的返回地址。

这将导致一个支离破碎的堆栈,未来的分配将覆盖“移动”内存。

有人能给我解释一下吗?

编辑:首先:非常感谢你的回答。但从我所了解到的情况来看,我仍然无法理解,为什么以下内容不像我期望的那样起作用:

代码语言:javascript
复制
struct MyStruct
{
    int iInteger;
    string strString;
    string strString2;
};

void MyFunc(vector<MyStruct>& vecStructs)
{
    MyStruct oNewStruct = { 8, "Hello", "Definetly more than 16 characters" };
    vecStructs.push_back(std::move(oNewStruct));

    // At this point, oNewStruct.String2 should be "", because its memory was stolen.
    // But only when I explicitly create a move-constructor in the form which was
    // stated by Yakk, it is really that case.
}

void main()
{
    vector<MyStruct> vecStructs;
    MyFunc(vecStructs);
}
EN

回答 2

Stack Overflow用户

发布于 2017-03-28 13:26:15

首先,std::move不移动,std::forward不向前移动。

std::move是对rvalue引用的强制转换。按照惯例,rvalue引用被视为“允许将数据移出的引用,因为调用者承诺他们真的不再需要该数据了”。

在栅栏的另一边,rvalue引用隐式绑定到std::move的返回值(有时是向前),在某些情况下,当从函数返回本地时,以及在使用临时或移出对象的成员时,引用到临时对象。

在函数中使用rvalue引用所发生的事情并不神奇。它不能直接在所讨论的对象中声明存储。然而,它可以撕下它的内脏;如果它能够以更快的方式完成操作,它就有权(按照惯例)处理它的参数内部状态。

现在,C++将自动为您编写一些移动构造函数。

代码语言:javascript
复制
struct MyStruct
{
  int iInteger;
  string strString;
};

在本例中,它将编写大致如下所示的内容:

代码语言:javascript
复制
MyStruct::MyStruct( MyStruct&& other ) noexcept(true) :
  iInteger( std::move(other.iInteger) ),
  strString( std::move(other.strString) )
{}

它将做一个元素级的移动构造。

当你移动一个整数时,没有什么有趣的事情发生。处理源整数的状态没有任何好处。

当你移动一个std::string,我们得到一些效率。C++标准描述了当您从一个std::string移动到另一个std::string时会发生什么。基本上,如果源std::string正在使用堆,则堆存储将传输到目标std::string

这是C++容器的一般模式;当您从它们移动时,它们会窃取源容器的“堆分配”存储,并在目标中重用它。

请注意,源std::string仍然是一个std::string,只是它的“内脏被撕开”。大多数像东西一样的容器都是空的,我不记得std::string是否提供了这样的保证(可能不是因为SBO),而且现在它并不重要。

简而言之,当你离开某物时,它的记忆不是“重用”的,而是它所拥有的记忆可以被重用。

在您的例子中,MyStruct有一个std::string,它可以使用堆分配的内存。此堆分配的内存可以移动到存储在MyStruct中的std::vector中。

再往下走一步,"Hello"可能会太短,以致于出现SBO (小缓冲区优化),而std::string根本不使用堆。对于这种特殊情况,由于moveing,可能几乎没有任何性能改进。

票数 53
EN

Stack Overflow用户

发布于 2017-03-28 13:31:00

您的示例可以简化为:

代码语言:javascript
复制
vector<string> vec;
string str; // populate with a really long string
vec.push_back(std::move(str));

这仍然引发了这样一个问题:“是否可以移动本地堆栈变量。”它只是删除了一些无关的代码,以使它更容易理解。

答案是肯定的。像上面这样的代码可以从std::move中受益,因为std::string--至少如果内容足够大--将其存储在堆中,即使变量在堆栈上。

如果您不使用std::move(),您可以期望上面这样的代码复制str的内容,这可能是任意大的。如果您确实使用了std::move(),那么只会复制字符串的直接成员(移动不需要“清除”旧位置),并且数据将被使用而不需要修改或复制。

这基本上是这样的区别:

代码语言:javascript
复制
char* str; // populate with a really long string
char* other = new char[strlen(str)+1];
strcpy(other, str);

vs

代码语言:javascript
复制
char* str; // populate with a really long string
char* other = str;

在这两种情况下,变量都在堆栈上。但数据并非如此。

如果有这样的情况,即所有数据都在堆栈上,比如具有“小字符串优化”效果的std::string,或者包含整数的结构,那么std::move()将不会为您购买任何东西。

票数 26
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43070571

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档