使用局部变量移动语义

cro*_*mmy 1 c++ stack move

我想知道从此示例中获取的以下代码:

是否可以 std::move 局部堆栈变量?

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)

我了解std::move()基本的工作原理,但在幕后有一些东西对我来说没有意义。

例如,如果NewStruct是在本地堆栈上创建的,如示例中所示。如果我在退出函数作用域之前使用移动语义来保存其数据免遭破坏,那么NewStruct被破坏就不会影响我的数据,这很好。但是这个信息不是还是放在栈里的吗?NewStruct如果我要再次扩展堆栈的使用,那么一旦堆栈增长并想要覆盖最初创建数据的位置,为什么这些信息不会面临被覆盖的危险呢?

添加第二个例子也许可以更好地表达我的观点:

    void MyFunc(vector<char*> &vecCharPointers)
    {
        char* myStr = {'H', 'e', 'l', 'l', 'o'};
        vecCharPointers.push_back(std::move(myStr));
    }

    int main()
    {
        vector<char*> vecCharPointers;
        char* cp = nullptr;
    }
Run Code Online (Sandbox Code Playgroud)

chr*_*ris 6

我认为您的误解在于数据的实际存储方式。结构体对象将在堆栈​​上,包含一个int和一个std::string对象,是的。但是,实际的字符串内容存储在空闲存储中,而不是堆栈中(忽略此处不重要的优化)。

现在让我们std::move从图片中删除并移动语义。会发生什么?该向量将在末尾创建一个新元素,该元素是从堆栈变量复制构造的。这意味着新对象将拥有值的副本int和值的副本std::string。复制std::string需要在空闲存储上分配另一个内存块并将数据复制到那里。此外,任何成员(例如尺寸)都会按您的原样复制int。当堆栈变量超出范围时,它的 int 和字符串将被销毁,这会导致字符串自行清理,而不以任何方式触及新副本。之后剩下的是向量末尾的新对象及其自己的数据副本。

如果你能原谅这个糟糕的图表:

显示堆栈与免费存储的复制图

搬家如何改变这一点?移动只是一种优化,目的是尽可能避免不必要的复制。复制是一种有效的移动策略,但如果你能做得更好,那就不是一个好的策略。使用std::move最终会导致向量在向量末尾创建新对象时移动堆栈对象。int 仍将被复制,因为那里没有优化。但是,字符串可以利用不再需要此免费存储数据的事实。新对象可以简单地窃取指针、复制大小等,并告诉移出的对象不要清理该空闲存储数据(转移所有权)。

如果您能原谅原始糟糕图表的稍微修改版本:

在此输入图像描述

我们不再为字符串分配一个单独的块,但仅此而已。其他数据都还是复制的。当向量元素被删除或向量被破坏时,实际的字符串数据将被向量元素清理。堆栈元素现在没有什么需要清理的,因为移动“窃取”了字符串数据而不是复制它。您可以将其视为将堆栈对象的指针清空,即使实现可以自由地以不同的方式表示空字符串。


我所说的并不完全适用于此,因为 的主要实现std::string足够聪明,可以避免小字符串的额外分配。这仅仅意味着字符串数据需要被复制,因为正如你所说,当原始对象复制时它就会消失。不过,任何额外的分配都可以进行移动优化。


至于您的第二个示例,您无法进行一般优化来移动原始指针;(通常)只需复制 4 或 8 个字节。将其移动到向量中会复制指针值,从而在函数结束后导致向量内出现悬空指针。myStr当函数结束时,函数内的指针将被销毁,并且不会以任何方式影响向量。

  • @anastaciu,无需担心此类事情。如果答案对任何人有帮助,它就完成了它的工作。 (2认同)