C++ の値カテゴリーとムーブセマンティクス

最終更新: 2021-01-25 02:14:28 +0800 +0800
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

ムーブセマンティクスはオーバーロード選択と型で定義された状態移転であり、所有権の 自動移転ではありません。まず式の値カテゴリーを確認し、次に所有権とライフタイムを 表すパラメーター型を選びます。

式のカテゴリー #

すべての void ではない式は、次のカテゴリーのいずれか 1 つに該当します。

  • lvalue はオブジェクト、関数、ビットフィールドを識別します。
  • xvalue はリソースを再利用できるオブジェクトを識別します。
  • prvalue は値を計算するか、オブジェクトを初期化します。

lvalue と xvalue は glvalue であり、prvalue と xvalue は rvalue です。名前付き 変数は型が T&& であっても lvalue 式です。

cpp
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 に束縛します。
cpp
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> は元のカテゴリーを再現できます。

cpp
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 で明示する。

独立した値を保存するコンストラクターは、値渡しで受け取り、パラメーターからムーブできます。

cpp
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 が空である、または容量を保持していると仮定しないでください。

cpp
std::vector<int> buffer = read_row();
rows.push_back(std::move(buffer));
buffer.clear(); // Valid, but capacity reuse is not guaranteed.

借用は所有者より長く存続してはなりません。所有権を持つ unique_ptr をムーブしても、 指示対象や既存の借用がすべて破棄されるわけではありませんが、新しい所有者を後で破棄 またはリセットすると、借用がダングリングする可能性があります。ムーブ操作は表現の 移転を解決しますが、ライフタイムを明示する必要性をなくすものではありません。

コメント

GitHub で閲覧・コメントする(投稿にはログインが必要です)