ムーブセマンティクスはオーバーロード選択と型で定義された状態移転であり、所有権の 自動移転ではありません。まず式の値カテゴリーを確認し、次に所有権とライフタイムを 表すパラメーター型を選びます。
式のカテゴリー #
すべての void ではない式は、次のカテゴリーのいずれか 1 つに該当します。
- lvalue はオブジェクト、関数、ビットフィールドを識別します。
- xvalue はリソースを再利用できるオブジェクトを識別します。
- prvalue は値を計算するか、オブジェクトを初期化します。
lvalue と xvalue は glvalue であり、prvalue と xvalue は rvalue です。名前付き
変数は型が T&& であっても lvalue 式です。
int& counter();
int&& cache_slot();
counter() = 42; // The call is an lvalue.
int&& slot = cache_slot();
consume(slot); // `slot` is an lvalue expression.
consume(std::move(slot)); // Casts it to an xvalue.
T を返す呼び出しは prvalue、T& を返す呼び出しは lvalue、T&& を返す呼び出し
は xvalue です。std::move は xvalue キャストを行うだけで、データを移動したり
ソースオブジェクトのライフタイムを終了させたりしません。
ムーブ構築はオーバーロード選択である #
参照の束縛には次の違いがあります。
T&は互換性のある lvalue に束縛します。const T&は lvalue、xvalue、prvalue に束縛します。T&&は xvalue と prvalue に束縛します。
std::string source = "payload";
std::string destination = std::move(source); // Selects a move constructor when viable.
選択されたコンストラクターが移転を定義します。確保領域を移転する場合も、小さな
表現をコピーする場合も、コピーと区別できない効果の場合もあります。ローカルを値で
返す場合は通常 return local; を使います。コピー省略で結果を直接構築でき、適用
されない場合も適格なローカルはオーバーロード選択で xvalue として扱えます。
return std::move(local); は名前付き戻り値最適化を妨げることがあります。
転送参照は呼び出し側のカテゴリーを保持する #
推論されるテンプレートパラメーターの T&& は転送参照です。lvalue 引数では T
が U& と推論され、rvalue では U と推論されます。参照折り畳みにより U& &&
は U& になるため、std::forward<T> は元のカテゴリーを再現できます。
template<class T>
void relay(T&& value) {
consume(std::forward<T>(value));
}std::move(value) は両方のケースを xvalue に変換し、lvalue の呼び出し側から予期せず
ムーブすることがあります。推論を使わない T&&(たとえば T がすでに固定された
クラステンプレートのメンバー)は通常の rvalue 参照であり、転送参照ではありません。
パラメーター設計は所有権から始める #
呼び出し先の契約を表すパラメーター形式を使います。
| 形式 | 契約 |
|---|---|
const T& | 呼び出し中に読み取り専用で借用する。 |
T& | 呼び出し中に借用して変更する。 |
T | 独立した値を受け取り、呼び出し側がコピーまたはムーブする。 |
T&& | 呼び出し先が消費できる rvalue を要求する。所有権は暗黙に移転しない。 |
std::unique_ptr<T> | 排他的所有権を移転する。 |
std::shared_ptr<T> | 呼び出し先が共有所有権を保持する場合に共有する。 |
T* | 借用する。null 許容性は API で明示する。 |
独立した値を保存するコンストラクターは、値渡しで受け取り、パラメーターからムーブできます。
class Message {
public:
explicit Message(std::string text) : text_{std::move(text)} {}
private:
std::string text_;
};lvalue の呼び出し側はパラメーターへ 1 回コピーし、rvalue の呼び出し側はムーブできます。 問い合わせや、呼び出し先が単に借用するオブジェクトには値渡しは不適切です。
所有権を示すために FILE*&& などの生ポインター rvalue 参照を使わないでください。
生ポインターのムーブはアドレスをコピーするだけなので、どちらの名前も同じリソースを
指します。呼び出しが破棄責任を移転する場合は、std::unique_ptr<T, Deleter> など
所有権を持つラッパーを使います。スマートポインターが所有するオブジェクトを使うだけ
の関数は、アクセスと null 許容性に応じて T&、const T&、T*、const T* を
受け取るべきで、スマートポインターのハンドルを受け取る必要はありません。
ムーブ元オブジェクトは存続する #
標準ライブラリのオブジェクトは、仕様に追加の記載がない限り、ムーブされた後も有効
だが未規定の値を持ちます。破棄、代入、その状態を事前条件が受け入れる操作は有効です。
ムーブ元の std::vector が空である、または容量を保持していると仮定しないでください。
std::vector<int> buffer = read_row();
rows.push_back(std::move(buffer));
buffer.clear(); // Valid, but capacity reuse is not guaranteed.
借用は所有者より長く存続してはなりません。所有権を持つ unique_ptr をムーブしても、
指示対象や既存の借用がすべて破棄されるわけではありませんが、新しい所有者を後で破棄
またはリセットすると、借用がダングリングする可能性があります。ムーブ操作は表現の
移転を解決しますが、ライフタイムを明示する必要性をなくすものではありません。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)