从 C++ 基础到掌握 — 第 14 集
复制和移动有什么区别?
该博客上撰写的文章是出于教学目的而创建的。 另外,文章中的信息可能不正确,请谨慎。
当将大对象插入向量或从函数返回对象时,似乎会发生大量复制。 在 C++ 中,复制(按原样复制值)和移动(接管不再需要的对象的资源)之间存在区别。
复制
复制会创建一个与原始对象具有相同值的新对象。
class Texture
{
public:
explicit Texture(std::vector<unsigned char> pixels)
: pixels(std::move(pixels))
{
}
private:
std::vector<unsigned char> pixels;
};
Texture original(LoadPixels());
Texture copied = original;
copied创建向量时,向量内部的数据也会被复制。 由于两个对象彼此独立,因此修改复制的对象不会更改原始对象。
小值的复制成本很小,但具有大缓冲区或许多字符串的对象可能会很昂贵。
移动
如果不再使用原始对象,则可以将内部资源传递给新对象。
Texture loaded(LoadPixels());
Texture equipped = std::move(loaded);
std::move不直接传输数据。 我们更改对象以将其视为可移动值,实际工作由移动构造函数或移动赋值运算符完成。
搬家了loaded仍然可以被销毁并可以分配新的值。 但是,您不应期望内部状态与以前相同。
loaded = Texture(LoadPixels()); //可以替换新值。
Rule of Zero
如果直接用new和delete管理内存,就必须一起考虑复制构造函数、复制赋值、移动构造函数、移动赋值和析构函数。 即使其中一个实现不正确,也可能会发生双重释放或内存泄漏。
class CharacterData
{
public:
std::string name;
std::vector<int> learnedSkills;
std::unique_ptr<Weapon> weapon;
};
使用标准容器和智能指针作为成员,每种类型都管理自己的资源。 这种不直接编写特殊成员函数的方法称为零规则。
unique_ptr 无法复制;它只能被移动。 此限制可防止在编译阶段意外复制唯一所有权。
如何传递值
只读的大对象const T&当函数取得所有权时,通常使用将其作为值接收然后移动它的方法。
class Party
{
public:
void Add(CharacterData character)
{
members.push_back(std::move(character));
}
private:
std::vector<CharacterData> members;
};
Party party;
CharacterData warrior;
party.Add(std::move(warrior));
CharacterData 有 unique_ptr 作为成员,因此不能复制,只能移动。 添加现有对象时,必须使用 std::move 传递所有权或传递临时对象。
到处添加 std::move 并不是最佳选择。
您必须首先决定它是一个需要复制的值还是一个您可以传递所有权的值。 移动并不是一种快速复制的语法,而是一种在转移资源后不依赖于原始状态的表达。