1. ホーム
  2. c++

[解決済み] ムーブセマンティクスとは何ですか?

2022-03-18 01:38:05

質問

ソフトウェア工学のラジオを聴き終えたところです Scott Meyersのポッドキャストインタビュー について C++0x . ほとんどの新機能は私にとって意味があり、1つの例外を除いて、私は今、実際にC++0xに興奮しています。私はまだ 移動セマンティクス ... 具体的にはどのようなものなのでしょうか?

どのように解決するのですか?

ムーブのセマンティクスを理解するには、サンプルコードが最もわかりやすいと思います。ヒープで割り当てられたメモリブロックへのポインタを保持するだけの、非常に単純な文字列クラスから始めましょう。

#include <cstring>
#include <algorithm>

class string
{
    char* data;

public:

    string(const char* p)
    {
        size_t size = std::strlen(p) + 1;
        data = new char[size];
        std::memcpy(data, p, size);
    }

メモリを自分で管理することにしたため、以下のように 三の法則 . 代入演算子を書くのを先延ばしにして、とりあえずデストラクタとコピーコンストラクタだけを実装することにします。

    ~string()
    {
        delete[] data;
    }

    string(const string& that)
    {
        size_t size = std::strlen(that.data) + 1;
        data = new char[size];
        std::memcpy(data, that.data, size);
    }

copyコンストラクタは、文字列オブジェクトをコピーすることの意味を定義しています。パラメータ const string& that は文字列型のすべての式に束縛され、次の例でコピーを作成することができます。

string a(x);                                    // Line 1
string b(x + y);                                // Line 2
string c(some_function_returning_a_string());   // Line 3

さて、ここからは移動のセマンティクスに関する重要な洞察です。最初の行で x を検査したいと思うかもしれないので、この深いコピーは本当に必要なのでしょうか? x を使用すると、後で非常に驚きます。 x は、何らかの形で変化していたのです。私が今言ったことに気づきましたか? x を3回(この文章を含めると4回)意味し、その間に 全く同じオブジェクト 毎回ですか?私たちは、このような表現を x "lvalues"です。

2行目と3行目の引数はlvalueではなくrvalueです。なぜなら、基礎となる文字列オブジェクトには名前がないので、クライアントは後の時点でそれらを再び検査する手段を持たないからです。 rvalueは一時的なオブジェクトを表し、次のセミコロンで(より正確には、語彙的にrvalueを含む完全な式の終わりで)破棄されます。これは重要なことです。 bc であれば、ソースの文字列で好きなことができますし クライアントには違いがわからない !

C++0x では、特に "rvalue reference" と呼ばれる新しいメカニズムが導入されています。 関数のオーバーロードでrvalueの引数を検出することができます。必要なのは、rvalue参照パラメータを持つコンストラクタを書くことだけです。そのコンストラクタの内部では、次のようなことができる。 好きなように のままにしておくと、ソースを いくつか 有効な状態です。

    string(string&& that)   // string&& is an rvalue reference to a string
    {
        data = that.data;
        that.data = nullptr;
    }

ここで何をしたのか?ヒープデータを深くコピーするのではなく、ポインタをコピーして、元のポインタをnullにしました(ソースオブジェクトのデストラクタから「delete[]」が「盗んだばかりのデータ」を解放するのを防ぐためです)。事実上、私たちは元々ソース文字列に属していたデータをquot;stolen"してしまったのです。ここでも重要なのは、どのような状況でもクライアントがソースが変更されたことを検知できないことです。ここでは実際にコピーを行わないので、このコンストラクタを「移動コンストラクタ」と呼びます。このコンストラクタの仕事は、リソースをコピーするのではなく、あるオブジェクトから別のオブジェクトに移動させることです。

おめでとうございます!これで移動のセマンティクスの基本が理解できましたね。続けて、代入演算子を実装してみましょう。もしあなたが コピーとスワップのイディオム 例外安全性に関連した素晴らしいC++のイディオムなので、覚えて帰ってきてください。

    string& operator=(string that)
    {
        std::swap(data, that.data);
        return *this;
    }
};

という質問に対して、「ここでは必要ない」というのが私の答えです(笑)。

パラメータを渡すことに注意してください。 that 値で ということで that は、他の文字列オブジェクトと同様に初期化する必要があります。まさに、どのように that は初期化されるのでしょうか?昔は C++98 この場合、答えは「コピーコンストラクタ」です。C++0x では、コンパイラは、代入演算子の引数が l 値か r 値かに基づいてコピー コンストラクタと移動コンストラクタのどちらかを選択します。

ということは、もしあなたが a = b は、その コピーコンストラクタ を初期化します。 that (なぜなら、式 b はl値です)、代入演算子はその内容を新しく作成された深いコピーと交換します。コピーを作り、そのコピーと中身を交換し、そしてスコープを出てコピーを取り除く。新しいことは何もない。

しかし、もしあなたが a = x + y は、その 移動コンストラクタ を初期化します。 that (なぜなら、式 x + y はr値である)ので、ディープコピーは行われず、効率的な移動のみが行われます。 that はまだ引数とは独立したオブジェクトですが、その構築は些細なことでした。 ヒープデータをコピーする必要がなく、ただ移動させるだけだからです。コピーする必要がなかったのは x + y はrvalueであり、ここでもrvalueで示される文字列オブジェクトからの移動は問題ない。

要約すると、コピーコンストラクタはディープコピーを作成します。 一方、移動コンストラクタはポインタをコピーするだけで、ソースのポインタをヌルに設定できます。この方法では、クライアントがオブジェクトを再び検査する方法がないため、ソース オブジェクトを NULL にしても問題ありません。

この例で、要点が伝わったでしょうか。rvalueの参照と移動のセマンティクスにはもっと多くのことがありますが、シンプルにするために意図的に省いています。もっと詳しく知りたい場合は、以下を参照してください。 補足回答 .