移动语义如何保存临时变量的数据

How does move semantics preserve data of temporary variables?

本文关键字:变量 数据 保存 语义 何保存 移动      更新时间:2023-10-16

我正在阅读这篇文章:什么是移动语义?

请注意,在那篇文章中给出的移动构造函数的例子是:

string(string&& that)  
{
    data = that.data;
    that.data = nullptr;
}

当我们使用string a(x+y)来构造一个新字符串时,我发现它令人困惑。由于x+y的结果是一个临时变量,它很快就会被破坏。这意味着在原始数据(应该存储在函数调用完成后清理的x+y的堆栈帧中)被销毁后,通过指针(data = that.data)进行复制实际上就是通过悬挂指针进行复制。将that.data设置为nullptr似乎没有帮助,因为堆栈帧无论如何都会被清理掉。

有人能解释为什么这不是一个问题吗?c++实际上是如何处理这种情况的?

当你这样做时:

string a(x + y);

它相当于:

string temp(x + y);
string a(move(temp));
//destroy temp

您引用的move构造函数的相关代码将a作为thisthat作为temp,因此它可以内联为:

string temp(x + y);
string a(/*uninitialized*/);
a.data = temp.data;
temp.data = nullptr;
//destroy temp

正如您所看到的,temp.data是被置空的,因此temp的析构函数变成了no-op,实际数据在a内部保留,正如预期的那样。

你的困惑似乎来自data的起源。在最简单的string实现中,string::data始终是动态分配的内存块:

string(const char *str)
{
    size_t len = strlen(str);
    data = new char[len + 1];
    strcpy(data, len);
}   
~string()
{
    delete[] data;
}

即使在堆栈上分配了string,例如tempa,甚至可能是xy,它们的data存储块也是动态的。

的确,现实世界中的string实现通常进行非动态短字符串优化。但如果你这样做,那么move构造函数(和任何其他成员函数)就会有点复杂。

由于x+y的结果是一个临时变量,它很快就会被破坏。这意味着在指针上复制(data=That.data)实际上就是在悬挂指针上复制

没有。复制指针,使新字符串现在具有数据,然后将临时对象的指针设置为nullptr,使临时对象销毁时不会删除字符串数据。

你可以在这个的小例子中看到它是如何工作的

#include <iostream>
struct Foo
{
    int * f;
    Foo(int size) : f(new int[size]) 
    { 
        for (int i = 0; i < size; i++)
            f[i] = i; 
    }
    Foo() : f(nullptr) {}
    ~Foo() { delete [] f; }
};
int main()
{
    int size = 10;
    Foo b;  // b is empty
    {
        Foo f(size);  // now f has an of size 10
        // if we now swap the contents like the move operation does
        b.f = f.f;
        f.f = nullptr;
    } // f goes out of scope and ~Foo() is called
    // now here b.f is valid as delete on nullptr did nothing
    for (int i = 0; i < size; i++)
            std::cout << b.f[i] << " ";
}

实时示例

string的情况下,数据存储在堆上,而不是堆栈帧中。"字符串"对象只包含指针,可能还包含一些附加数据(例如长度)。因此,您显示的move构造函数有效地"剥夺"了that对象对其数据的所有权。第二行,将nullptr分配给临时的数据指针,是必要的,以避免同一临时的析构函数删除我们窃取的数据(因为在nullptr上调用delete保证不会产生任何效果)。