【问题标题】:How does move semantics preserve data of temporary variables?移动语义如何保存临时变量的数据?
【发布时间】:2016-04-11 00:31:41
【问题描述】:

我正在阅读这篇文章:What are move semantics?

请注意,该帖子中针对移动构造函数给出的示例是:

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

当我们使用string a(x+y) 构造一个新字符串时,我发现它很混乱。由于x+y 的结果是一个临时变量,它很快就会被销毁。这意味着复制指针 (data = that.data) 确实会复制一个悬空指针,在原始数据(应该存储在函数调用完成后被清理的x+y 的堆栈帧中)被销毁之后.似乎将 that.data 设置为 nullptr 无济于事,因为无论如何都会清理堆栈帧。

谁能解释为什么这不是问题?而c++实际上是如何处理这种情况的?

【问题讨论】:

  • 移动一个对象通常会改变目标和源对象(目标窃取数据而让源一无所有——也许是数据交换)
  • x+y 创建一个临时字符串,其中包含指向包含相关数据的堆内存的指针 data。如果临时超出范围,它在堆上拥有的内存将被删除。但是您引用的移动构造函数会以一种不再拥有该内存的方式更改临时对象-因此它将超出范围而不会实际触及其内容。相反,新创建的a 现在拥有该内存(其data 指向数据,最初由x+y 拥有)。

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


【解决方案1】:

当你这样做时:

string a(x + y);

相当于:

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

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

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

如您所见,temp.data 是被清空的那个,所以temp 的析构函数变成了空操作,而实际数据在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 实现通常会进行非动态短字符串优化。但是如果你这样做,那么移动构造函数(和任何其他成员函数)会有点复杂。

【讨论】:

  • @OneZero:太长了,无法在评论中解释。请查看我的更新答案。
  • 如果不是string,而是只有int data[5](而不是使用new)的自定义对象类型怎么办?那样的话,是不是就不能简单的复制data指针了?
  • @OneZero 在这种情况下data 不是指针而是数组,移动构造函数根本没用。
【解决方案2】:

由于 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] << " ";
}

Live Example

【讨论】:

    【解决方案3】:

    string 的情况下,数据存储在堆中,而不是堆栈帧中。 “字符串”对象仅包含指针和一些附加数据(例如长度)。因此,您显示的移动构造函数有效地“抢夺”了 that 对象的数据所有权。第二行,将nullptr 分配给临时对象的数据指针是必要的,以避免同一个临时对象的析构函数删除我们窃取的数据(因为保证在nullptr 上调用delete 无效)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-28
      • 1970-01-01
      • 2013-10-08
      • 2014-09-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多