是否有可能std :: move本地堆栈变量?

All*_*uer 20 c++ move move-semantics c++11

请考虑以下代码:

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);
}
Run Code Online (Sandbox Code Playgroud)

为什么这样做?

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

但是当函数返回并且NewStruct超出范围时.即使编译器不会从堆栈中删除最初存在的结构所占用的内存,他至少也要删除先前存储的返回地址.

这将导致碎片堆栈,未来的分配将覆盖"移动"的内存.

有人可以向我解释一下吗?


编辑:首先:非常感谢您的回答.但是从我学到的东西,我仍然无法理解,为什么以下不能像我期望的那样工作:

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);
}
Run Code Online (Sandbox Code Playgroud)

Yak*_*ont 26

首先,std::move不动,std::forward不转发.

std::move是对右值参考的强制转换.按照惯例,rvalue引用被视为"允许您将数据移出的引用,因为调用者承诺它们实际上不再需要该数据".

在围栏的另一侧,rvalue引用隐式绑定到std::move临时对象的返回值(在某些情况下,从函数返回本地时),以及使用临时或移动的成员时来自对象.

在采用右值参考的函数中发生的事情并不神奇.它不能直接在相关对象内声明存储.然而,它可以撕掉它的内脏; 它有权(按照惯例)弄乱其参数内部状态,如果它能以这种方式更快地进行操作.

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

struct MyStruct
{
  int iInteger;
  string strString;
};
Run Code Online (Sandbox Code Playgroud)

在这种情况下,它会写出大致如下所示的内容:

MyStruct::MyStruct( MyStruct&& other ) noexcept(true) :
  iInteger( std::move(other.iInteger) ),
  strString( std::move(other.strString) )
{}
Run Code Online (Sandbox Code Playgroud)

即,它将执行元素移动构造.

移动整数时,没有任何有趣的事情发生.弄乱源整数的状态没有任何好处.

移动时std::string,我们可以获得一些效率.C++标准描述了当你从一个移动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 ,可能几乎没有性能改进.

  • @matt 在原始类型上移动会复制。移动原始类型(数组或结构)的聚合会进行复制。然而,拥有两个原始数据副本几乎没有可观察到的影响。所以编译器可以经常使用 as-if 规则来忽略它们的存在。最常见的问题是它们必须具有不同的地址,从而阻止省略;但是,超出范围的本地地址是未定义的。在标准库 cpntsoners 上移动(数组除外)确实避免了堆重新分配。允许移动 std 唯一锁,而不允许复制。所以情况各不相同。通常,资源拥有类型 (2认同)

Joh*_*nck 10

您的示例可以简化为:

vector<string> vec;
string str; // populate with a really long string
vec.push_back(std::move(str));
Run Code Online (Sandbox Code Playgroud)

这仍然提出了一个问题,"是否可以移动本地堆栈变量." 它只是删除了一些无关的代码,使其更容易理解.

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

如果你不使用std::move(),你可以期望像上面这样的代码复制内容str,这可能是任意大的.如果使用std::move(),则只复制字符串的直接成员(移动不需要将旧位置"清零"),并且将使用这些数据而无需修改或复制.

这基本上是这个之间的区别:

char* str; // populate with a really long string
char* other = new char[strlen(str)+1];
strcpy(other, str);
Run Code Online (Sandbox Code Playgroud)

VS

char* str; // populate with a really long string
char* other = str;
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,变量都在堆栈中.但数据不是.

如果你有一个真正所有数据都在堆栈中的情况,比如有效std::string的"小字符串优化",或者包含整数的结构,那么std::move()你什么都不买.