Menu
Coddy logo textTech

ムーブコンストラクタ

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 21/104。

ムーブコンストラクタは、一時オブジェクトからリソースをコピーするのではなく、転送します。コピーコンストラクタがデータを複製するのに対し、ムーブコンストラクタはリソースを「盗み」、元のオブジェクトを有効ですが空の状態にします。これにより、元のオブジェクトがどのみち破棄される場合に、コストの高いディープコピーを避けることができます。

ムーブコンストラクタは、&& で表される右辺値参照を受け取ります:

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    // ムーブコンストラクタ
    Buffer(Buffer&& other) noexcept {
        data = other.data;       // ポインタを奪う
        size = other.size;
        other.data = nullptr;    // ソースを有効な状態にする
        other.size = 0;
    }
    
    ~Buffer() { delete[] data; }
};

コピーとの主な違いは、新しいメモリを割り当てないことです。単に既存のメモリの所有権を取得し、ソースのポインタを nullptr に設定することで、そのデストラクタがデータを削除しないようにします。

ムーブコンストラクタは、一時オブジェクトから初期化する場合、または std::move() を使用する場合に呼び出されます:

Buffer createBuffer() {
    return Buffer(1000);    // ムーブコンストラクタが使用される (戻り値)
}

Buffer b1(1000);
Buffer b2(std::move(b1));   // 明示的なムーブ - b1 は空になります

ムーブコンストラクタは、可能な限り noexcept とマークしてください。これにより、コンパイラに対してその操作が例外をスローしないことを伝え、std::vector のようなコンテナにおいて重要な最適化が可能になります。

challenge icon

チャレンジ

簡単

ムーブコンストラクタがどのようにリソースの所有権を効率的に転送するかを実証する、データパケットシステムを構築しましょう。動的に割り当てられたバイト配列を管理する DataPacket クラスを作成し、データをコピーする代わりに「盗む」ムーブコンストラクタを実装します。

コードを整理するために、2つのファイルを作成します:

  • DataPacket.h: 整数のペイロードデータを管理する DataPacket クラスを定義します。クラスには以下のものを含める必要があります:
    • プライベートメンバ:整数配列へのポインタ (payload)、要素数のための size、およびパケットを識別するための packetId (string)
    • パケットIDとサイズを受け取り、配列を割り当て、0から size-1 までの値で埋めるパラメータ付きコンストラクタ。"Packet <id> created with size <size>" と出力してください
    • ソースオブジェクトからペイロードの所有権を転送する、noexcept とマークされたムーブコンストラクタ。"Packet <id> moved" と出力してください。ソースを有効な空の状態 (nullptr, size 0) にすることを忘れないでください
    • ポインタが null でない場合にメモリを解放し、"Packet <id> destroyed" と出力するデストラクタ
    • 現在のサイズを返す getSize() メソッド
    • パケットIDを返す getId() メソッド
    • ペイロード内のすべての要素の合計を返す getSum() メソッド (ペイロードが null の場合は 0 を返します)
  • main.cpp: 入力からパケットIDとサイズを読み取り、ムーブコンストラクションを実証します。その後:
    • 入力値を使用して original という名前の DataPacket を作成します
    • "Original - ID: <id>, Size: <size>, Sum: <sum>" と出力します
    • std::move() を使用して original からムーブすることで、transferred という名前の新しいパケットを作成します
    • "After move:" と出力します
    • "Original - ID: <id>, Size: <size>, Sum: <sum>" と出力します
    • "Transferred - ID: <id>, Size: <size>, Sum: <sum>" と出力します

入力形式は以下の通りです:

  • 1行目:パケットID (string)
  • 2行目:サイズ (integer)

ムーブ後、元のパケットはサイズ 0、合計 0 になり(データが転送されたため)、転送されたパケットが元のデータをすべて保持している必要があります。これにより、ムーブセマンティクスがポインタの所有権を転送するだけで、コストのかかるディープコピーをどのように回避するかが示されます。

main.cpp で #include "DataPacket.h" を使用してヘッダーファイルをインクルードし、std::move() のために <utility> をインクルードすることを忘れないでください。

チートシート

ムーブコンストラクタは、一時オブジェクトからリソースをコピーするのではなく転送することで、高コストなディープコピーを回避します。これは**右辺値参照** (&&) を受け取り、リソースを「盗み」、元のオブジェクトを有効ですが空の状態にします。

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    // ムーブコンストラクタ
    Buffer(Buffer&& other) noexcept {
        data = other.data;       // ポインタを盗む
        size = other.size;
        other.data = nullptr;    // 元のオブジェクトを有効な状態にする
        other.size = 0;
    }
    
    ~Buffer() { delete[] data; }
};

ムーブコンストラクタは、一時オブジェクトから初期化する場合や、std::move() を使用する場合に呼び出されます:

Buffer createBuffer() {
    return Buffer(1000);    // ムーブコンストラクタが使用される(戻り値)
}

Buffer b1(1000);
Buffer b2(std::move(b1));   // 明示的なムーブ - b1は空になる

標準コンテナでの最適化を有効にするために、可能な限りムーブコンストラクタに noexcept を指定してください。

自分で試してみよう

#include <iostream>
#include <string>
#include <utility>
#include "DataPacket.h"

using namespace std;

int main() {
    // 入力を読み込む
    string packetId;
    int size;
    cin >> packetId;
    cin >> size;

    // TODO: 入力値を使用して 'original' という名前の DataPacket を作成する

    // TODO: "Original - ID: <id>, Size: <size>, Sum: <sum>" を出力する

    // TODO: original からムーブして 'transferred' という名前の新しいパケットを作成する
    // Hint: std::move() を使用する

    // TODO: "After move:" を出力する

    // TODO: "Original - ID: <id>, Size: <size>, Sum: <sum>" を出力する

    // TODO: "Transferred - ID: <id>, Size: <size>, Sum: <sum>" を出力する

    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン