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

时间:2017-03-28 13:12:47

标签: c++ c++11 move move-semantics

请考虑以下代码:

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超出范围时。即使编译器不会从堆栈中删除最初存在的结构占用的内存,他至少也要删除以前存储的返回地址。

这将导致碎片堆叠,未来的分配将覆盖&#34;移动&#34;存储器中。

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

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

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);
}

2 个答案:

答案 0 :(得分:19)

首先,std::move不移动,std::forward不转发。

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

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

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

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

struct MyStruct
{
  int iInteger;
  string strString;
};

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

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,只有一个“胆量被撕掉”。大多数像容器这样的容器都是空的,我不记得MyStruct是否提供了保证(可能不是由于SBO),而且它现在并不重要。

简而言之,当你从某个东西移开时,它的内存不会被“重用”,但它拥有的内存可以被重用。

在您的情况下,std::stringMyStruct可以使用堆分配的内存。可以将此堆分配的内存移动到std::vector中存储的"Hello"

在兔子洞中走得更远,std::string可能太短以至于SBO发生(小缓冲区优化),而move根本不使用堆。对于这种特殊情况,由于.bye,可能几乎没有性能提升。

答案 1 :(得分:10)

您的示例可以简化为:

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(),则只会复制字符串的直接成员(移动不需要将旧位置“清零”),并且数据将在不进行修改或复制的情况下使用。

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

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

VS

char* str; // populate with a really long string
char* other = str;

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

如果你有一个真正所有数据都在堆栈上的情况,例如有效“小字符串优化”的std::string或包含整数的结构,那么std::move()会买什么都没有。