ムーブコンストラクタ
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 のようなコンテナにおいて重要な最適化が可能になります。
チャレンジ
簡単ムーブコンストラクタがどのようにリソースの所有権を効率的に転送するかを実証する、データパケットシステムを構築しましょう。動的に割り当てられたバイト配列を管理する 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;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス