我已经阅读了关于复制构造函数和移动语义的无数篇文章。我觉得我好像排序'理解发生了什么,但是很多解释都没有留下真正发生在幕后的事情(这是导致我混淆的原因)。
例如:
string b(x + y);
string(string&& that)
{
data = that.data;
that.data = 0;
}
对象在内存中实际发生了什么?所以你有一些对象'b'
需要x + y
这是一个右值然后调用移动构造函数。这真的让我感到困惑......为什么这样?
我理解的好处是“移动”。数据而不是复制数据,但我在这里丢失的是当我尝试将每个对象/参数在内存级别发生的事情拼凑起来时。
很抱歉,如果这听起来令人困惑,那么谈论它甚至会使我自己感到困惑。
编辑:
总之,我理解'为什么'副本构造函数和移动构造函数......我只是不理解'如何'。
答案 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+
按值返回,因此必须使用与x
和y
字符串完全无关的新缓冲区创建临时...
[ 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)移动语义。