通过以下对象复制构造函数和移动语义

时间:2014-12-11 05:18:18

标签: c++ c++11

我已经阅读了关于复制构造函数和移动语义的无数篇文章。我觉得我好像排序'理解发生了什么,但是很多解释都没有留下真正发生在幕后的事情(这是导致我混淆的原因)。

例如:

string b(x + y);

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

对象在内存中实际发生了什么?所以你有一些对象'b'需要x + y这是一个右值然后调用移动构造函数。这真的让我感到困惑......为什么这样?

我理解的好处是“移动”。数据而不是复制数据,但我在这里丢失的是当我尝试将每个对象/参数在内存级别发生的事情拼凑起来时。

很抱歉,如果这听起来令人困惑,那么谈论它甚至会使我自己感到困惑。

编辑:

总之,我理解'为什么'副本构造函数和移动构造函数......我只是不理解'如何'。

3 个答案:

答案 0 :(得分:2)

正在进行的是一个复杂的对象通常不会完全基于堆栈。我们来看一个示例对象:

class String {
public:
  // happy fun API
private:
  size_t size;
  char* data;
};

与大多数字符串一样,我们的字符串是一个字符数组。它本质上是一个保持字符数组和适当大小的对象。

如果是副本,则涉及两个步骤。首先复制size,然后复制data。但是data只是一个指针。因此,如果我们复制对象然后修改原始,两个地方都指向相同的数据,我们的副本会发生变化。这不是我们想要的。

所以必须做的就是做我们在第一次制作对象new data到适当大小时所做的事情。

因此,当我们复制对象时,我们需要执行以下操作:

String::String(String const& copy) {
  size = copy.size;
  data = new int[size];
  memcpy(data, copy.data, size);
}

但另一方面,如果我们只需要移动数据,我们可以做类似的事情:

String::String(String&& copy) {
  size = copy.size;
  data = copy.data;
  copy.size = 0;
  copy.data = nullptr; // So copy's dtor doesn't try to free our data.
}

现在在幕后,指针只是有点......传递给我们。我们没有必要再分配任何信息。这就是移动是首选的原因。在堆上分配和复制内存可能是一项非常昂贵的操作,因为它不会在堆栈本地发生,而是在其他地方发生,因此必须获取内存,它可能不在缓存中,等

答案 1 :(得分:1)

... (x + y);

让我们假设短字符串优化不起作用 - 因为字符串实现不使用它或字符串值太长。 operator+按值返回,因此必须使用与xy字符串完全无关的新缓冲区创建临时...

[ string { const char* _p_data; ... } ]
                           \
                            \-------------------------(heap)--------[ "hello world!" ];

Sans优化,用于为string构造函数准备参数 - “在”考虑构造函数将对参数执行的操作之前。

string b(x + y);

这里调用string(string&&)构造函数,因为编译器知道上面的临时值适合移动。当构造函数开始运行时,它的指向文本的指针是未初始化的 - 类似于下图,其中临时显示的是上下文:

[ string { const char* _p_data; ... } ]
                           \
                            \-------------------------(heap)--------[ "hello world!" ];


[ string b { const char* _p_data; ... } ]
                           \
                            \----? uninitialised

b的移动构造函数的作用是从临时文件中窃取现有的堆缓冲区。

                          nullptr
                         /
[ string { const char* _p_data; ... } ]

                             -------------------------(heap)--------[ "hello world!" ];
                            /
                           /
[ string b { const char* _p_data; ... } ]

还需要将临时的_p_data设置为nullptr,以确保当临时的析构函数运行时,delete[]缓冲区现在被认为是{{1}所拥有的缓冲区}}。 (移动构造函数也将“移动”其他数据成员 - “容量”值,指向“结束”位置的指针或“大小”值等。)

所有这些都避免让b的构造函数创建第二个堆缓冲区,将所有文本复制到其中,然后再对b临时缓冲区执行额外的工作。

答案 2 :(得分:0)

(x + y)为您提供字符串值。您希望将其存储在b中而不复制它。这可以在C ++ 11之前很久就实现,并且可以通过Return Value Optimization(RVO)移动语义。