C++ 基礎からマスターまで — 第14回
コピーと移動は何が異なりますか?
本ブログ作成文は、講義を目指して製作されました。 また、作成された文章の情報も間違っている可能性がありますので、ご注意ください。
vectorに大きなオブジェクトを置いたり、関数からオブジェクトを返したりすると、コピーがたくさん起こるように見えます。 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が作成されると、ベクトル内部データもコピーされます。 2つのオブジェクトは互いに独立しているため、コピーを変更してもオリジナルは変わりません。
小さい値はコピーコストがほとんどありませんが、大きなバッファや文字列が多いオブジェクトはコストがかかる可能性があります。
移動
ソースを使用しなくなった場合は、内部リソースを新しいオブジェクトに渡すことができます。
Texture loaded(LoadPixels());
Texture equipped = std::move(loaded);
std::moveが直接データを移動するわけではない。 オブジェクトを移動可能な値として扱うように変更し、実際の操作は移動コンストラクタまたは移動代入演算子によって実行されます。
移動loadedはまだ破壊でき、新しい値を代入することもできる。 しかし、内部状態が以前と同じだと期待してはならない。
loaded = Texture(LoadPixels()); //新しい値を代入することは可能です。
Rule of Zero
直接メモリをnewとdeleteで管理する場合は、コピーコンストラクタ、コピー代入、移動コンストラクタ、移動代入、デストラクタを一緒に悩まなければなりません。 1つでも誤って実装すると、二重解放やメモリリークが発生する可能性があります。
class CharacterData
{
public:
std::string name;
std::vector<int> learnedSkills;
std::unique_ptr<Weapon> weapon;
};
標準コンテナとスマートポインタをメンバとして使用すると、各タイプが独自のリソースを管理します。 このように特別メンバー関数を直接作成しない方法をRule of Zeroと呼ぶ。
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を付けることは最適化ではありません。
コピーが必要な値なのか、所有権を渡すことができる値なのか、まず決定しなければならない。 移動はクイックコピーの文法ではなく、リソースを渡した後、元の状態に依存しないという表現だ。