移動セマンティクスとは何ですか?

1777
dicroce 2010-06-24 12:46.

C ++ 0xに関するScottMeyersのソフトウェアエンジニアリングラジオポッドキャストインタビューを聞き終えたところです。新機能のほとんどは私には理にかなっており、1つを除いて、私は実際にC ++ 0xに興奮しています。私はまだ移動セマンティクスを取得していません...それは正確には何ですか?

11 answers

2582
fredoverflow 2010-06-25 02:40.

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

#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);
    }

自分でメモリを管理することを選択したので、3つのルールに従う必要があります。代入演算子の記述を延期し、今のところデストラクタとコピーコンストラクタのみを実装します。

    ~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);
    }

コピーコンストラクタは、文字列オブジェクトをコピーすることの意味を定義します。このパラメーター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何らかの変更があった場合は非常に驚かれるからです。私がちょうどx3回(この文を含めると4回)言って、毎回まったく同じオブジェクトを意味していることに気づきましたか?x「左辺値」などの式を呼び出します。

2行目と3行目の引数は左辺値ではなく右辺値です。これは、基になる文字列オブジェクトに名前がないため、クライアントが後でそれらを再度検査する方法がないためです。右辺値は、次のセミコロンで破棄される一時オブジェクトを示します(より正確には、右辺値を字句的に含む完全式の最後)。bとの初期化中に、cソース文字列を使用してやりたいことが何でもでき、クライアントが違いを認識できなかったため、これは重要です。

C ++ 0xでは、「右辺値参照」と呼ばれる新しいメカニズムが導入されています。これにより、特に、関数のオーバーロードを介して右辺値引数を検出できます。私たちがしなければならないのは、右辺値参照パラメーターを使用してコンストラクターを作成することだけです。そのコンストラクタ内では行うことができます私たちが望むものを限り、私たちがそれを残すように、ソースといくつかの有効な状態:

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

ここで何をしましたか?ヒープデータを深くコピーする代わりに、ポインタをコピーしてから元のポインタをnullに設定しました(ソースオブジェクトのデストラクタからの「delete []」が「盗まれたばかりのデータ」を解放しないようにするため)。実際には、元々ソース文字列に属していたデータが「盗まれ」ました。繰り返しになりますが、重要な洞察は、どのような状況でも、クライアントがソースが変更されたことを検出できないということです。ここでは実際にはコピーを行わないため、このコンストラクターを「移動コンストラクター」と呼びます。その仕事は、リソースをコピーするのではなく、あるオブジェクトから別のオブジェクトにリソースを移動することです。

おめでとうございます。これで、移動セマンティクスの基本を理解できました。代入演算子を実装して続けましょう。コピーとスワップのイディオムに慣れていない場合は、それを学び、戻ってきてください。これは、例外安全性に関連する素晴らしいC ++イディオムだからです。

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

ええ、それだけですか?「右辺値の参照はどこにありますか?」あなたは尋ねるかもしれません。「ここでは必要ありません!」私の答えです:)

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

したがって、と言うa = bと、コピーコンストラクターが初期化されthat(式bが左辺値であるため)、代入演算子は内容を新しく作成されたディープコピーと交換します。これが、コピーとスワップのイディオムの定義そのものです。コピーを作成し、内容をコピーとスワップしてから、スコープを離れてコピーを削除します。ここでは何も新しいことはありません。

ただし、と言うa = x + yと、moveコンストラクターが初期化れるthatため(式x + yが右辺値であるため)、深いコピーは含まれず、効率的な移動のみが行われます。thatはまだ引数から独立したオブジェクトですが、ヒープデータをコピーする必要がなく、移動するだけなので、その構成は簡単でした。x + yは右辺値であるため、コピーする必要はありませんでした。また、右辺値で示される文字列オブジェクトから移動しても問題ありません。

要約すると、コピーコンストラクターはディープコピーを作成します。これは、ソースが変更されないままである必要があるためです。一方、moveコンストラクターは、ポインターをコピーしてから、ソース内のポインターをnullに設定するだけです。クライアントにはオブジェクトを再度検査する方法がないため、この方法でソースオブジェクトを「無効化」しても問題ありません。

この例が要点を理解したことを願っています。参照を再評価し、セマンティクスを移動することは、単純にするために意図的に省略したものがたくさんあります。詳細が必要な場合は、私の補足回答を参照してください。

1095
fredoverflow 2012-07-19 01:24.

私の最初の答えは、移動セマンティクスの非常に単純化された導入であり、単純にするために多くの詳細が意図的に省略されていました。しかし、セマンティクスを動かすにはまだまだたくさんのことがあり、ギャップを埋めるために2番目の答えが必要だと思いました。最初の答えはすでにかなり古く、まったく別のテキストに置き換えるのは正しくないと感じました。それでも最初の紹介としては役立つと思います。しかし、もっと深く掘り下げたい場合は、以下を読んでください:)

Stephan T. Lavavejは、貴重なフィードバックを提供するために時間をかけました。ステファン、ありがとうございました!

前書き

移動セマンティクスにより、特定の条件下で、オブジェクトが他のオブジェクトの外部リソースの所有権を取得できます。これは2つの点で重要です。

  1. 高価なコピーを安価な動きに変える。例については、私の最初の答えを参照してください。オブジェクトが少なくとも1つの外部リソースを(直接またはそのメンバーオブジェクトを介して間接的に)管理しない場合、移動セマンティクスはコピーセマンティクスに勝る利点を提供しないことに注意してください。その場合、オブジェクトをコピーして移動することは、まったく同じことを意味します。

    class cannot_benefit_from_move_semantics
    {
        int a;        // moving an int means copying an int
        float b;      // moving a float means copying a float
        double c;     // moving a double means copying a double
        char d[64];   // moving a char array means copying a char array
    
        // ...
    };
    
  2. 安全な「移動専用」タイプの実装。つまり、コピーは意味がありませんが、移動は意味があります。例には、ロック、ファイルハンドル、および一意の所有権セマンティクスを持つスマートポインターが含まれます。注:この回答ではstd::auto_ptr、非推奨のC ++ 98標準ライブラリテンプレートについて説明しています。これは、std::unique_ptrC ++ 11で置き換えられました。中級のC ++プログラマーは、おそらく少なくともある程度は精通しておりstd::auto_ptr、表示される「移動セマンティクス」のため、C ++ 11での移動セマンティクスについて説明するための良い出発点のように思われます。YMMV。

動きとは何ですか?

C ++ 98標準ライブラリは、と呼ばれる固有の所有権セマンティクスを持つスマートポインタを提供しますstd::auto_ptr<T>。に慣れていない場合auto_ptr、その目的は、例外が発生した場合でも、動的に割り当てられたオブジェクトが常に解放されることを保証することです。

{
    std::auto_ptr<Shape> a(new Triangle);
    // ...
    // arbitrary code, could throw exceptions
    // ...
}   // <--- when a goes out of scope, the triangle is deleted automatically

珍しいことauto_ptrは、その「コピー」動作です。

auto_ptr<Shape> a(new Triangle);

      +---------------+
      | triangle data |
      +---------------+
        ^
        |
        |
        |
  +-----|---+
  |   +-|-+ |
a | p | | | |
  |   +---+ |
  +---------+

auto_ptr<Shape> b(a);

      +---------------+
      | triangle data |
      +---------------+
        ^
        |
        +----------------------+
                               |
  +---------+            +-----|---+
  |   +---+ |            |   +-|-+ |
a | p |   | |          b | p | | | |
  |   +---+ |            |   +---+ |
  +---------+            +---------+

注どのようにの初期化bではaないではない三角形をコピーし、代わりに三角形の所有権を転送aしますb。また、「a移動 b」または「三角形がから移動」とも言います。三角形自体は常にメモリ内の同じ場所にとどまるため、これは混乱を招くように聞こえるかもしれません。a b

オブジェクトを移動するということは、オブジェクトが管理するリソースの所有権を別のオブジェクトに譲渡することを意味します。

のコピーコンストラクタは、auto_ptrおそらく次のようになります(やや簡略化されています)。

auto_ptr(auto_ptr& source)   // note the missing const
{
    p = source.p;
    source.p = 0;   // now the source no longer owns the object
}

危険で無害な動き

についての危険なことauto_ptrは、構文的にコピーのように見えるものが実際には動きであるということです。移動元でメンバー関数を呼び出そうとすると、auto_ptr未定義の動作が呼び出されるため、移動元のauto_ptr後に後を使用しないように十分に注意する必要があります。

auto_ptr<Shape> a(new Triangle);   // create triangle
auto_ptr<Shape> b(a);              // move a into b
double area = a->area();           // undefined behavior

しかしauto_ptr常に危険であるとは限りませ。ファクトリ関数は、次の完全に優れたユースケースですauto_ptr

auto_ptr<Shape> make_triangle()
{
    return auto_ptr<Shape>(new Triangle);
}

auto_ptr<Shape> c(make_triangle());      // move temporary into c
double area = make_triangle()->area();   // perfectly safe

両方の例が同じ構文パターンに従うことに注意してください。

auto_ptr<Shape> variable(expression);
double area = expression->area();

それでも、一方は未定義の動作を呼び出しますが、もう一方は呼び出しません。では、式aとの違いは何make_triangle()ですか?どちらも同じタイプではないですか?確かにそうですが、異なる値のカテゴリがあります

値のカテゴリ

もちろん、表現の間にいくつかの深遠な違いが存在しなければならないa意味auto_ptr変数を、そして表現make_triangle()返す関数の呼び出し表すauto_ptrので、新鮮な一時的な作成、値によってauto_ptrオブジェクトが呼び出されるたびに。a一例である左辺値に対して、make_triangle()一例である右辺値

のような左辺値からの移動aは危険です。後でa、を介してメンバー関数を呼び出そうとし、未定義の動作を呼び出す可能性があるためです。一方、などの右辺値からの移動make_triangle()は完全に安全です。コピーコンストラクターがそのジョブを実行した後は、一時を再び使用できないためです。上記の一時的なものを表す表現はありません。単にmake_triangle()もう一度書くと、別の一時的なものになります。実際、移動元の一時的なものは、次の行ですでになくなっています。

auto_ptr<Shape> c(make_triangle());
                                  ^ the moved-from temporary dies right here

文字lrは、割り当ての左側と右側に歴史的な起源があることに注意してください。これはC ++ではもはや当てはまりません。代入の左側に表示できない左辺値(配列や代入演算子のないユーザー定義型など)と、表示できる右辺値(クラス型のすべての右辺値)があるためです。代入演算子を使用)。

クラス型の右辺値は、評価によって一時オブジェクトが作成される式です。通常の状況では、同じスコープ内の他の式が同じ一時オブジェクトを示すことはありません。

右辺値参照

左辺値からの移動は潜在的に危険であることがわかりましたが、右辺値からの移動は無害です。C ++に左辺値引数と右辺値引数を区別する言語サポートがある場合、左辺値からの移動を完全に禁止するか、少なくとも呼び出しサイトで左辺値からの移動を明示的にして、誤って移動しないようにすることができます。

この問題に対するC ++ 11の答えは、右辺値参照です。右辺値参照は、右辺値にのみバインドする新しい種類の参照であり、構文はX&&です。古き良き参照X&は、左辺値参照として知られるようになりました。(これは参照への参照でX&&ないことに注意してください。C++にはそのようなものはありません。)

constミックスに投入すると、すでに4種類の参照があります。Xそれらはどのような型の式にバインドできますか?

            lvalue   const lvalue   rvalue   const rvalue
---------------------------------------------------------              
X&          yes
const X&    yes      yes            yes      yes
X&&                                 yes
const X&&                           yes      yes

実際には、忘れることができますconst X&&。右辺値からの読み取りに制限されていることはあまり役に立ちません。

右辺値参照X&&は、右辺値にのみバインドする新しい種類の参照です。

暗黙の変換

右辺値参照にはいくつかのバージョンがあります。バージョン2.1以降、からへの暗黙的な変換がある場合、右辺値参照X&&は異なるタイプのすべての値カテゴリにもバインドされます。その場合、型の一時が作成され、右辺値参照がその一時にバインドされます。YYXX

void some_function(std::string&& r);

some_function("hello world");

上記の例で"hello world"は、はタイプの左辺値ですconst char[12]。そこからの暗黙的な変換であるのでconst char[12]通じconst char*std::string、タイプの一時的std::stringに作成され、rその一時的にバインドされています。これは、右辺値(式)と一時(オブジェクト)の区別が少しぼやけている場合の1つです。

コンストラクターを移動する

X&&パラメーターを持つ関数の便利な例は、moveコンストラクター X::X(X&& source)です。その目的は、管理対象リソースの所有権をソースから現在のオブジェクトに譲渡することです。

C ++ 11では、右辺値参照を利用するにstd::auto_ptr<T>置き換えられましstd::unique_ptr<T>た。の簡略版を開発して説明しunique_ptrます。まず、生のポインターをカプセル化し、演算子->とをオーバーロードする*ので、クラスはポインターのように感じられます。

template<typename T>
class unique_ptr
{
    T* ptr;

public:

    T* operator->() const
    {
        return ptr;
    }

    T& operator*() const
    {
        return *ptr;
    }

コンストラクターがオブジェクトの所有権を取得し、デストラクタがオブジェクトを削除します。

    explicit unique_ptr(T* p = nullptr)
    {
        ptr = p;
    }

    ~unique_ptr()
    {
        delete ptr;
    }

次に、興味深い部分である移動コンストラクターがあります。

    unique_ptr(unique_ptr&& source)   // note the rvalue reference
    {
        ptr = source.ptr;
        source.ptr = nullptr;
    }

この移動コンストラクターは、auto_ptrコピーコンストラクターが実行したことを正確に実行しますが、右辺値でのみ指定できます。

unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a);                 // error
unique_ptr<Shape> c(make_triangle());   // okay

aは左辺値であるため、2行目はコンパイルに失敗しますが、パラメーターunique_ptr&& sourceは右辺値にのみバインドできます。これはまさに私たちが望んでいたことです。危険な動きは決して暗示的であってはなりません。make_triangle()は右辺値であるため、3行目は問題なくコンパイルされます。移動コンストラクターは、所有権を一時からに転送しcます。繰り返しますが、これはまさに私たちが望んでいたことです。

移動コンストラクターは、管理対象リソースの所有権を現在のオブジェクトに転送します。

ムーブ代入演算子

最後に欠けているのは、ムーブ代入演算子です。その仕事は、古いリソースを解放し、その引数から新しいリソースを取得することです。

    unique_ptr& operator=(unique_ptr&& source)   // note the rvalue reference
    {
        if (this != &source)    // beware of self-assignment
        {
            delete ptr;         // release the old resource

            ptr = source.ptr;   // acquire the new resource
            source.ptr = nullptr;
        }
        return *this;
    }
};

ムーブ代入演算子のこの実装が、デストラクタとムーブコンストラクタの両方のロジックを複製する方法に注意してください。コピーアンドスワップのイディオムに精通していますか?また、移動と交換のイディオムとして移動セマンティクスに適用することもできます。

    unique_ptr& operator=(unique_ptr source)   // note the missing reference
    {
        std::swap(ptr, source.ptr);
        return *this;
    }
};

これsourceはタイプの変数でunique_ptrあるため、moveコンストラクターによって初期化されます。つまり、引数はパラメータに移動されます。移動コンストラクター自体に右辺値参照パラメーターがあるため、引数は右辺値である必要があります。制御フローはの閉じ括弧に到達するとoperator=source自動的に古いリソースを解放し、スコープ外になります。

ムーブ代入演算子は、管理対象リソースの所有権を現在のオブジェクトに譲渡し、古いリソースを解放します。移動と交換のイディオムは、実装を簡素化します。

左辺値からの移動

場合によっては、左辺値から移動したいことがあります。つまり、コンパイラーが左辺値を右辺値であるかのように処理して、安全でない可能性がある場合でも、moveコンストラクターを呼び出すことができるようにしたい場合があります。この目的のために、C ++ 11はstd::move、ヘッダー内で呼び出される標準ライブラリ関数テンプレートを提供します<utility>。この名前はstd::move、左辺値を右辺値にキャストするだけなので、少し残念です。それ自体は何も動かしませ。それは単に移動を可能にするだけです。たぶんそれはstd::cast_to_rvalueorという名前になっているはずですがstd::enable_move、私たちは今ではその名前に固執しています。

左辺値から明示的に移動する方法は次のとおりです。

unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a);              // still an error
unique_ptr<Shape> c(std::move(a));   // okay

3行目以降はa、三角形を所有していないことに注意してください。明示的に書くことでstd::move(a)、私たちの意図を明確にしたので、それは大丈夫です。「親愛なるコンストラクター、a初期化するために好きなことをしてください。もうc気にしaません。自由にa。を使ってください。」

std::move(some_lvalue) 左辺値を右辺値にキャストして、後続の移動を可能にします。

X値

std::move(a)は右辺値ですが、その評価では一時オブジェクト作成されないことに注意してください。この難問により、委員会は3番目の価値カテゴリーを導入することを余儀なくされました。それは伝統的な意味では右辺値ではないにもかかわらず、右辺値参照にバインドすることができるものを、と呼ばれてはxValue(値を期限切れ)。従来の右辺値は、prvalues(純粋な右辺値)に名前が変更されました

prvaluesとxvaluesはどちらも右辺値です。X値と左辺値は両方ともglvalues(一般化左辺値)です。関係は、図を使用すると簡単に把握できます。

        expressions
          /     \
         /       \
        /         \
    glvalues   rvalues
      /  \       /  \
     /    \     /    \
    /      \   /      \
lvalues   xvalues   prvalues

xvaluesだけが本当に新しいことに注意してください。残りは、名前の変更とグループ化によるものです。

C ++ 98の右辺値は、C ++ 11ではprvaluesとして知られています。前の段落の「rvalue」のすべての出現箇所を「prvalue」に精神的に置き換えます。

機能からの移行

これまで、ローカル変数と関数パラメーターへの移動を見てきました。ただし、反対方向への移動も可能です。関数が値で戻る場合、呼び出しサイトのオブジェクト(おそらくローカル変数または一時的なものですが、任意の種類のオブジェクトである可能性があります)はreturn、moveコンストラクターへの引数としてステートメントの後の式で初期化されます。

unique_ptr<Shape> make_triangle()
{
    return unique_ptr<Shape>(new Triangle);
}          \-----------------------------/
                  |
                  | temporary is moved into c
                  |
                  v
unique_ptr<Shape> c(make_triangle());

おそらく驚くべきことに、自動オブジェクト(として宣言されていないローカル変数static)も暗黙的に関数から移動できます。

unique_ptr<Shape> make_square()
{
    unique_ptr<Shape> result(new Square);
    return result;   // note the missing std::move
}

移動コンストラクターが左辺値resultを引数として受け入れるのはなぜですか?のスコープはresult間もなく終了し、スタックの巻き戻し中に破棄されます。resultどういうわけか変わった後、誰も文句を言うことができなかったでしょう。制御フローが呼び出し元に戻ったとき、resultもう存在しません!そのため、C ++ 11には、を記述せずに関数から自動オブジェクトを返すことができる特別なルールがありますstd::move。実際、自動オブジェクトを関数から移動するために使用しないでください。std::moveこれにより、「名前付き戻り値の最適化」(NRVO)が禁止されます。

std::move自動オブジェクトを関数から移動するために使用しないでください。

どちらのファクトリ関数でも、戻り値の型は値であり、右辺値の参照ではないことに注意してください。右辺値参照は引き続き参照であり、いつものように、自動オブジェクトへの参照を返さないでください。次のように、コンパイラをだましてコードを受け入れさせた場合、呼び出し元はぶら下がっている参照になってしまいます。

unique_ptr<Shape>&& flawed_attempt()   // DO NOT DO THIS!
{
    unique_ptr<Shape> very_bad_idea(new Square);
    return std::move(very_bad_idea);   // WRONG!
}

右辺値参照によって自動オブジェクトを返さないでください。移動はstd::move、単に右辺値を右辺値参照にバインドすることによってではなく、移動コンストラクターによって排他的に実行されます。

メンバーに移動する

遅かれ早かれ、あなたはこのようなコードを書くつもりです:

class Foo
{
    unique_ptr<Shape> member;

public:

    Foo(unique_ptr<Shape>&& parameter)
    : member(parameter)   // error
    {}
};

基本的に、コンパイラはそれparameterが左辺値であると文句を言います。その型を見ると、右辺値参照が表示されますが、右辺値参照は単に「右辺値にバインドされている参照」を意味します。参照自体が右辺値であるという意味ではありませ。確かに、parameterは名前の付いた単なる通常の変数です。parameterコンストラクターの本体内で何度でも使用でき、常に同じオブジェクトを示します。暗黙のうちにそこから移動することは危険であるため、言語はそれを禁止しています。

名前付き右辺値参照は、他の変数と同じように左辺値です。

解決策は、手動で移動を有効にすることです。

class Foo
{
    unique_ptr<Shape> member;

public:

    Foo(unique_ptr<Shape>&& parameter)
    : member(std::move(parameter))   // note the std::move
    {}
};

parameterの初期化後、それはもう使用されていないと主張することができますmemberstd::move戻り値と同じようにサイレントに挿入する特別なルールがないのはなぜですか?おそらくコンパイラの実装者に負担がかかりすぎるからでしょう。たとえば、コンストラクター本体が別の変換ユニットにある場合はどうなりますか?対照的に、戻り値ルールは、単にシンボルテーブルをチェックして、returnキーワードの後の識別子が自動オブジェクトを示しているかどうかを判断する必要があります。

parameterby値を渡すこともできます。のような移動専用タイプの場合unique_ptr、確立されたイディオムはまだないようです。個人的には、インターフェイスの乱雑さが少なくなるため、値を渡すことを好みます。

特別会員機能

C ++ 98は、オンデマンドで、つまり、コピーコンストラクタ、コピー割り当て演算子、およびデストラクタの3つの特別なメンバー関数を暗黙的に宣言します。

X::X(const X&);              // copy constructor
X& X::operator=(const X&);   // copy assignment operator
X::~X();                     // destructor

右辺値参照にはいくつかのバージョンがあります。バージョン3.0以降、C ++ 11は、オンデマンドで2つの追加の特別なメンバー関数を宣言します。ムーブコンストラクターとムーブ代入演算子です。VC10もVC11もバージョン3.0にまだ準拠していないため、自分で実装する必要があることに注意してください。

X::X(X&&);                   // move constructor
X& X::operator=(X&&);        // move assignment operator

これらの2つの新しい特殊メンバー関数は、どの特殊メンバー関数も手動で宣言されていない場合にのみ暗黙的に宣言されます。また、独自のムーブコンストラクターまたはムーブ代入演算子を宣言した場合、コピーコンストラクターもコピー代入演算子も暗黙的に宣言されません。

これらのルールは実際にはどういう意味ですか?

アンマネージリソースを使用せずにクラスを作成する場合、5つの特別なメンバー関数を自分で宣言する必要はなく、正しいコピーセマンティクスと移動セマンティクスを無料で取得できます。それ以外の場合は、特別なメンバー関数を自分で実装する必要があります。もちろん、クラスが移動セマンティクスの恩恵を受けない場合は、特別な移動操作を実装する必要はありません。

コピー代入演算子とムーブ代入演算子は、引数を値でとって、単一の統合代入演算子に融合できることに注意してください。

X& X::operator=(X source)    // unified assignment operator
{
    swap(source);            // see my first answer for an explanation
    return *this;
}

このようにして、実装する特別なメンバー関数の数が5から4に減少します。ここでは例外安全性と効率性の間にトレードオフがありますが、私はこの問題の専門家ではありません。

転送参照(以前はユニバーサル参照と呼ばれていました

次の関数テンプレートについて考えてみます。

template<typename T>
void foo(T&&);

T&&一見すると右辺値参照のように見えるため、右辺値にのみバインドすることを期待するかもしれません。しかし、T&&結局のところ、左辺値にもバインドします。

foo(make_triangle());   // T is unique_ptr<Shape>, T&& is unique_ptr<Shape>&&
unique_ptr<Shape> a(new Triangle);
foo(a);                 // T is unique_ptr<Shape>&, T&& is unique_ptr<Shape>&

引数の型の右辺値である場合はXTあることを推測されX、したがって、T&&手段X&&。これは誰もが期待することです。ただし、引数が型の左辺値である場合、X特別な規則により、Tと推定されるためX&、のT&&ような意味になりX& &&ます。ただし、C ++にはまだ参照への参照の概念がないため、型X& &&はに折りたたまX&ます。これは最初は混乱して役に立たないように聞こえるかもしれませんが、完全な転送には参照の折りたたみが不可欠です(ここでは説明しません)。

T &&は右辺値参照ではなく、転送参照です。また、左辺値にバインドします。この場合T、とT&&は両方とも左辺値の参照です。

関数テンプレートを右辺値に制約する場合は、SFINAEをタイプ特性と組み合わせることができます。

#include <type_traits>

template<typename T>
typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type
foo(T&&);

移転の実施

参照の折りたたみについて理解したので、次のようstd::moveに実装します。

template<typename T>
typename std::remove_reference<T>::type&&
move(T&& t)
{
    return static_cast<typename std::remove_reference<T>::type&&>(t);
}

ご覧のとおりmove、転送参照のおかげであらゆる種類のパラメーターを受け入れT&&、右辺値参照を返します。std::remove_reference<T>::typeそれ以外の場合は、種類の左辺値のためのためのメタ関数呼び出しが必要でX、戻り値の型は次のようになりX& &&に崩壊することになります、X&。のでt(という名前の右辺値参照は左辺値であることを覚えておいてください)常に左辺値ですが、私たちが結合したいt右辺値参照に、我々は明示的にキャストする必要がありt、正しい戻り値の型に。右辺値参照を返す関数の呼び出しは、それ自体がxvalueです。これで、xvaluesがどこから来るかがわかりました;)

などの右辺値参照を返す関数の呼び出しはstd::movexvalueです。

この例でtは、自動オブジェクトではなく、呼び出し元から渡されたオブジェクトを示しているため、右辺値参照による戻りは問題ないことに注意してください。

80
sbi 2010-06-24 13:12.

移動セマンティクスは、右辺値参照に基づいています
右辺値は一時オブジェクトであり、式の最後で破棄されます。現在のC ++では、右辺const値は参照にのみバインドします。C ++ 1xは、右辺値オブジェクトへの参照であるconst、スペルト小麦の非右辺値参照を許可しT&&ます。
右辺値は式の終わりで消滅するため、そのデータ盗むことができます。別のオブジェクトにコピーする代わりに、そのデータをそのオブジェクトに移動します。

class X {
public: 
  X(X&& rhs) // ctor taking an rvalue reference, so-called move-ctor
    : data_()
  {
     // since 'x' is an rvalue object, we can steal its data
     this->swap(std::move(rhs));
     // this will leave rhs with the empty data
  }
  void swap(X&& rhs);
  // ... 
};

// ...

X f();

X x = f(); // f() returns result as rvalue, so this calls move-ctor

上記のコードでは、古いコンパイラでは、のコピーコンストラクタを使用しての結果f()コピーされます。コンパイラがmoveセマンティクスをサポートし、move-constructorがある場合は、代わりにそれが呼び出されます。その引数は右辺値であるため、もう必要ないことがわかり、その値を盗むことができます。値がされるように移動から戻った無名の一時的なからに(のデータは一方で、空に初期化し、割り当て後に破棄されますこれは、一時的に移動されます)。xXXrhs
f()xxX

62
Greg Hewgill 2010-06-24 12:53.

実質的なオブジェクトを返す関数があるとします。

Matrix multiply(const Matrix &a, const Matrix &b);

このようなコードを書くとき:

Matrix r = multiply(a, b);

次に、通常のC ++コンパイラは、の結果の一時オブジェクトを作成しmultiply()、コピーコンストラクタを呼び出して初期化しr、一時的な戻り値を破棄します。C ++ 0xの移動セマンティクスを使用すると、「移動コンストラクター」を呼び出してr、その内容をコピーして初期化し、一時値を破棄せずに破棄できます。

これは、(おそらくMatrix上記の例のように)コピーされるオブジェクトが、その内部表現を格納するためにヒープに追加のメモリを割り当てる場合に特に重要です。コピーコンストラクターは、内部表現の完全なコピーを作成するか、参照カウントとコピーオンライトセマンティクスを内部的に使用する必要があります。移動コンストラクターは、ヒープメモリをそのままにして、Matrixオブジェクト内のポインターをコピーするだけです。

31
James McNellis 2010-06-24 13:32.

移動セマンティクスの詳細な説明に本当に興味がある場合は、元の論文「C ++言語に移動セマンティクスサポートを追加する提案」を読むことを強くお勧めします。

それは非常にアクセスしやすく、読みやすく、それらが提供する利点の優れた事例になります。WG21 Webサイトで利用可能な移動セマンティクスに関する他のより最近の最新の論文がありますが、これはトップレベルの観点から物事にアプローチし、ざらざらした言語の詳細にはあまり触れないため、おそらく最も簡単です。

29
Dave Abrahams 2012-04-09 09:47.

移動セマンティクスとは、ソース値が不要になったときにリソースをコピーするのではなく、リソースを転送することです。

C ++ 03では、オブジェクトはコピーされることが多く、コードが値を再び使用する前に破棄または割り当てられるだけです。たとえば、関数から値で返す場合(RVOが起動しない限り)、返す値は呼び出し元のスタックフレームにコピーされ、スコープ外になって破棄されます。これは多くの例の1つにすぎません。ソースオブジェクトが一時的な場合の値渡し、sortアイテムを再配置するだけのアルゴリズム、超過したvector場合の再割り当てcapacity()などを参照してください。

このようなコピー/破棄のペアが高価な場合、それは通常、オブジェクトが重いリソースを所有しているためです。たとえば、それぞれが独自の動的メモリを持つオブジェクトのvector<string>配列を含む動的に割り当てられたメモリブロックをstring所有している場合があります。このようなオブジェクトのコピーにはコストがかかります。ソース内の動的に割り当てられたブロックごとに新しいメモリを割り当て、すべての値をコピーする必要があります。次に、コピーしたすべてのメモリの割り当てを解除する必要があります。ただし、大きな移動とvector<string>、いくつかのポインタ(動的メモリブロックを参照)を宛先にコピーし、ソースでそれらをゼロにすることを意味します。

25
Peregring-lk 2013-08-19 05:57.

簡単な(実用的な)用語で:

オブジェクトのコピーとは、その「静的」メンバーをコピーしnew、その動的オブジェクトのオペレーターを呼び出すことを意味します。正しい?

class A
{
   int i, *p;

public:
   A(const A& a) : i(a.i), p(new int(*a.p)) {}
   ~A() { delete p; }
};

ただし、オブジェクトを移動することは(実際的な観点から繰り返しますが)、動的オブジェクトのポインターをコピーすることだけを意味し、新しいオブジェクトを作成することは意味しません。

しかし、それは危険ではありませんか?もちろん、動的オブジェクトを2回破壊することもできます(セグメンテーション違反)。したがって、それを回避するには、ソースポインタを「無効化」して、2回破壊しないようにする必要があります。

class A
{
   int i, *p;

public:
   // Movement of an object inside a copy constructor.
   A(const A& a) : i(a.i), p(a.p)
   {
     a.p = nullptr; // pointer invalidated.
   }

   ~A() { delete p; }
   // Deleting NULL, 0 or nullptr (address 0x0) is safe. 
};

わかりましたが、オブジェクトを移動すると、ソースオブジェクトが役に立たなくなりますね。もちろんですが、特定の状況では非常に便利です。最も明白なものは、匿名オブジェクト(一時、右辺値オブジェクト、...、別の名前で呼び出すことができます)を使用して関数を呼び出す場合です。

void heavyFunction(HeavyType());

その場合、匿名オブジェクトが作成され、次に関数パラメーターにコピーされ、その後削除されます。したがって、ここでは、匿名オブジェクトを必要とせず、時間とメモリを節約できるため、オブジェクトを移動することをお勧めします。

これは、「右辺値」参照の概念につながります。これらは、受信したオブジェクトが匿名であるかどうかを検出するためにのみC ++ 11に存在します。「左辺値」が割り当て可能なエンティティ(=演算子の左側)であることはすでにご存知だと思います。そのため、左辺値として機能するには、オブジェクトへの名前付き参照が必要です。右辺値は正反対で、名前付き参照のないオブジェクトです。そのため、匿名オブジェクトと右辺値は同義語です。そう:

class A
{
   int i, *p;

public:
   // Copy
   A(const A& a) : i(a.i), p(new int(*a.p)) {}

   // Movement (&& means "rvalue reference to")
   A(A&& a) : i(a.i), p(a.p)
   {
      a.p = nullptr;
   }

   ~A() { delete p; }
};

この場合、型のオブジェクトをA「コピー」する必要がある場合、コンパイラは、渡されたオブジェクトに名前が付けられているかどうかに応じて、左辺値参照または右辺値参照を作成します。そうでない場合は、move-constructorが呼び出され、オブジェクトが一時的であることがわかり、動的オブジェクトをコピーする代わりに移動して、スペースとメモリを節約できます。

「静的」オブジェクトは常にコピーされることを覚えておくことが重要です。静的オブジェクト(ヒープ上ではなくスタック内のオブジェクト)を「移動」する方法はありません。したがって、オブジェクトに動的メンバーが(直接的または間接的に)ない場合の「移動」/「コピー」の区別は関係ありません。

オブジェクトが複雑で、デストラクタがライブラリの関数の呼び出し、他のグローバル関数の呼び出しなど、他の副次的な効果を持っている場合は、フラグを使用して動きを通知する方がよいでしょう。

class Heavy
{
   bool b_moved;
   // staff

public:
   A(const A& a) { /* definition */ }
   A(A&& a) : // initialization list
   {
      a.b_moved = true;
   }

   ~A() { if (!b_moved) /* destruct object */ }
};

そのため、コードは短く(nullptr動的メンバーごとに割り当てを行う必要はありません)、より一般的です。

その他の一般的な質問:どのような違いであるA&&とはconst A&&?もちろん、前者の場合はオブジェクトを変更でき、後者の場合は変更できませんが、実際的な意味はありますか?2番目のケースでは、オブジェクトを変更できないため、オブジェクトを無効にする方法はなく(可変フラグなどを除く)、コピーコンストラクターと実際的な違いはありません。

そして、完璧な転送とは何ですか?「右辺値参照」は、「呼び出し元のスコープ」内の名前付きオブジェクトへの参照であることを知っておくことが重要です。ただし、実際のスコープでは、右辺値参照はオブジェクトの名前であるため、名前付きオブジェクトとして機能します。右辺値参照を別の関数に渡す場合は、名前付きオブジェクトを渡すため、オブジェクトは一時オブジェクトのように受信されません。

void some_function(A&& a)
{
   other_function(a);
}

オブジェクトaは、の実際のパラメータにコピーされますother_function。オブジェクトをa一時オブジェクトとして引き続き処理する場合は、次のstd::move関数を使用する必要があります。

other_function(std::move(a));

この行で、std::movea右辺値にキャストし、other_function名前のないオブジェクトとしてオブジェクトを受け取ります。もちろん、other_function名前のないオブジェクトを処理するための特定のオーバーロードがない場合、この区別は重要ではありません。

それは完璧な転送ですか?そうではありませんが、私たちは非常に近いです。完全転送は、テンプレートを操作する場合にのみ役立ちます。つまり、オブジェクトを別の関数に渡す必要がある場合、名前付きオブジェクトを受け取った場合、そのオブジェクトは名前付きオブジェクトとして渡され、そうでない場合は、名前のないオブジェクトのように渡したい:

template<typename T>
void some_function(T&& a)
{
   other_function(std::forward<T>(a));
}

これは、完全な転送を使用する典型的な関数のシグネチャであり、C ++ 11でstd::forward。を使用して実装されています。この関数は、テンプレートのインスタンス化のいくつかのルールを利用します。

 `A& && == A&`
 `A&& && == A&&`

したがって、TAT = A&)への左辺値参照である場合、aA &&& => A&)も参照されます。Tがへの右辺値参照である場合Aa(A && && => A &&)も参照します。どちらの場合も、aは実際のスコープ内の名前付きオブジェクトですがT、呼び出し元スコープの観点からは、その「参照タイプ」の情報が含まれています。この情報(T)はテンプレートパラメータとしてに渡されforward、「a」はのタイプに応じて移動されるかどうかに応じて移動されTます。

21
Terry Mahaffey 2010-06-24 12:56.

これはコピーセマンティクスに似ていますが、すべてのデータを複製する代わりに、「移動」元のオブジェクトからデータを盗むことができます。

14
snk_kid 2010-06-24 12:58.

コピーセマンティクスが正しい意味を知っていますか?これは、コピー可能な型があることを意味します。ユーザー定義型の場合、これを定義するには、コピーコンストラクターと代入演算子を明示的に作成するか、コンパイラーが暗黙的に生成します。これはコピーを行います。

移動セマンティクスは基本的に、非定数であるr値参照(&&(はい2アンパサンド)を使用する新しいタイプの参照)を受け取るコンストラクターを持つユーザー定義型です。これは移動コンストラクターと呼ばれ、代入演算子についても同じです。したがって、moveコンストラクタは、ソース引数からメモリをコピーする代わりに、ソースから宛先にメモリを「移動」します。

いつそれをしたいですか?std :: vectorは例です。たとえば、一時的なstd :: vectorを作成し、それを関数から返します。

std::vector<foo> get_foos();

関数が戻るときにコピーコンストラクターからオーバーヘッドが発生します(C ++ 0xではそうなります)std :: vectorには、コピーする代わりにmoveコンストラクターがあり、ポインターを設定して「move」を動的に割り当てることができます新しいインスタンスへのメモリ。これは、std :: auto_ptrを使用した所有権の譲渡のセマンティクスのようなものです。

8
Andreas DM 2016-02-25 14:00.

移動セマンティクスの必要性を説明するために、移動セマンティクスを使用しないこの例を考えてみましょう。

これは、型のオブジェクトを受け取りT、同じ型のオブジェクトを返す関数ですT

T f(T o) { return o; }
  //^^^ new object constructed

上記関数の使用は、値によって呼び出し、この関数はオブジェクトと呼ばれていることを意味するがされなければならない構築機能によって使用されます。
関数も戻るため、戻り値用に別の新しいオブジェクトが作成されます。

T b = f(a);
  //^ new object constructed

2つの新しいオブジェクトが作成されました。そのうちの1つは、関数の実行中にのみ使用される一時オブジェクトです。

戻り値から新しいオブジェクトが作成されると、コピーコンストラクターが呼び出されて、一時オブジェクトの内容が新しいオブジェクトにコピーさます。関数が完了すると、関数で使用されている一時オブジェクトはスコープ外になり、破棄されます。


それでは、コピーコンストラクターが何をするかを考えてみましょう。

最初にオブジェクトを初期化し、次にすべての関連データを古いオブジェクトから新しいオブジェクトにコピーする必要があります。
クラスによっては、データが非常に多いコンテナである可能性があり、それは多くの時間メモリ使用量を表す可能性があります

// Copy constructor
T::T(T &old) {
    copy_data(m_a, old.m_a);
    copy_data(m_b, old.m_b);
    copy_data(m_c, old.m_c);
}

移動セマンティクスを使用すると、コピーするのではなくデータを移動するだけで、この作業のほとんどを不快感を少なくすることができます。

// Move constructor
T::T(T &&old) noexcept {
    m_a = std::move(old.m_a);
    m_b = std::move(old.m_b);
    m_c = std::move(old.m_c);
}

データを移動するには、データを新しいオブジェクトに再度関連付ける必要があります。そして、コピーはまったく行わません

これは、rvalue参照を使用して実行されます。リファレンスはかなりのように動作し、一つの重要な違いが参照:右辺値参照を移動させることができる左辺値はできません。
rvaluelvalue

cppreference.comから:

強力な例外保証を可能にするために、ユーザー定義の移動コンストラクターは例外をスローしないようにする必要があります。実際、標準のコンテナーは通常、コンテナー要素を再配置する必要がある場合に、移動とコピーのどちらかを選択するためにstd :: move_if_noexceptに依存しています。コピーコンストラクターと移動コンストラクターの両方が提供されている場合、オーバーロード解決では、引数が右辺値(名前のない一時などのprvalueまたはstd :: moveの結果などのxvalue)の場合は移動コンストラクターが選択され、次の場合はコピーコンストラクターが選択されます。引数は左辺値(名前付きオブジェクトまたは左辺値参照を返す関数/演算子)です。コピーコンストラクターのみが提供されている場合、すべての引数カテゴリがそれを選択し(右辺値はconst参照にバインドできるため、constへの参照を取得する限り)、移動が利用できない場合に移動のフォールバックをコピーします。多くの場合、移動コンストラクターは、観察可能な副作用が発生する場合でも最適化されます。コピーの省略を参照してください。コンストラクターは、右辺値参照をパラメーターとして受け取る場合、「移動コンストラクター」と呼ばれます。何かを移動する義務はありません。クラスには移動するリソースが必要ありません。また、パラメータがaの場合の許容可能な(ただし、意味がない)場合のように、「moveコンストラクタ」はリソースを移動できない場合があります。 const右辺値参照(const T &&)。

8
Chris B 2016-11-19 13:12.

私はそれを正しく理解することを確実にするためにこれを書いています。

移動セマンティクスは、大きなオブジェクトの不要なコピーを回避するために作成されました。彼の著書「TheC ++ Programming Language」のBjarneStroustrupは、デフォルトで不要なコピーが発生する2つの例を使用しています。1つは2つの大きなオブジェクトの交換、2つはメソッドからの大きなオブジェクトの返還です。

2つの大きなオブジェクトを交換するには、通常、最初のオブジェクトを一時オブジェクトにコピーし、2番目のオブジェクトを最初のオブジェクトにコピーし、一時オブジェクトを2番目のオブジェクトにコピーします。組み込み型の場合、これは非常に高速ですが、大きなオブジェクトの場合、これらの3つのコピーには長い時間がかかる可能性があります。「ムーブ代入」を使用すると、プログラマーはデフォルトのコピー動作をオーバーライドし、代わりにオブジェクトへの参照をスワップできます。これは、コピーがまったくなく、スワップ操作がはるかに高速であることを意味します。ムーブ代入は、std :: move()メソッドを呼び出すことで呼び出すことができます。

デフォルトでは、メソッドからオブジェクトを返すには、呼び出し元がアクセスできる場所にローカルオブジェクトとそれに関連するデータのコピーを作成する必要があります(ローカルオブジェクトは呼び出し元がアクセスできず、メソッドが終了すると消えるため)。組み込み型が返される場合、この操作は非常に高速ですが、大きなオブジェクトが返される場合、これには長い時間がかかる可能性があります。移動コンストラクターを使用すると、プログラマーはこのデフォルトの動作をオーバーライドし、代わりに、呼び出し元に返されるオブジェクトをローカルオブジェクトに関連付けられたヒープデータにポイントすることで、ローカルオブジェクトに関連付けられたヒープデータを「再利用」できます。したがって、コピーは必要ありません。

ローカルオブジェクト(つまり、スタック上のオブジェクト)の作成を許可しない言語では、すべてのオブジェクトがヒープに割り当てられ、常に参照によってアクセスされるため、これらのタイプの問題は発生しません。

Related questions

MORE COOL STUFF

「水曜日」シーズン1の中心には大きなミステリーがあります

「水曜日」シーズン1の中心には大きなミステリーがあります

Netflixの「水曜日」は、典型的な10代のドラマ以上のものであり、実際、シーズン1にはその中心に大きなミステリーがあります.

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ケイト・ミドルトンは、州の夕食会と州の訪問中にカミラ・パーカー・ボウルズからスポットライトを奪いたくなかった、と専門家は言う.

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンが、オリビア・ワイルドが彼女とハリー・スタイルズとの間の「難しい」が「非常に友好的」な分割を恒久的にすることを望んでいる理由を見つけてください.

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする 

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする&nbsp;

エリザベス女王の死後、ケイト・ミドルトンが舞台裏で「非常に困難な時期」を過ごしていたと伝えられている理由を調べてください.

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セント ヘレナ島のジェイコブズ ラダーは 699 段の真っ直ぐ上る階段で、頂上に到達すると証明書が発行されるほどの難易度です。

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

パンデミックは終わったかもしれないが、Covid-19 は終わっていない

パンデミックは終わったかもしれないが、Covid-19 は終わっていない

2021 年 6 月 8 日にニューヨーク市で開催された covid-19 パンデミックで亡くなった人々の命を偲び、祝うために、ネーミング ザ ロスト メモリアルズが主催するイベントと行進の最中に、グリーンウッド墓地の正門から記念碑がぶら下がっています。週末、ジョー・バイデン大統領は、covid-19 パンデミックの終息を宣言しました。これは、過去 2 年以上にわたり、公の場でそうするための長い列の中で最新のものです。

デビル・イン・オハイオの予告編は、エミリー・デシャネルもオハイオにいることを明らかにしています

デビル・イン・オハイオの予告編は、エミリー・デシャネルもオハイオにいることを明らかにしています

オハイオ州のエミリー・デシャネル みんな早く来て、ボーンズが帰ってきた!まあ、ショーボーンズではなく、彼女を演じた俳優. エミリー・デシャネルに最後に会ってからしばらく経ちました.Emily Deschanel は、長期にわたるプロシージャルな Bones の Temperance “Bones” Brennan としてよく知られています。

ドナルド・トランプはFBIのマー・ア・ラーゴ襲撃映像をリリースする予定ですか?

ドナルド・トランプはFBIのマー・ア・ラーゴ襲撃映像をリリースする予定ですか?

どうやら、ドナルド・トランプに近い人々は、今月初めにFBIによって家宅捜索された彼のMar-a-Lago財産からの映像を公開するよう彼に勧めています. 前大統領はテープを公開するかどうかを確認していませんが、息子はフォックス・ニュースにそうなるだろうと語った.

Andor は、他の Star Wars ショーから大きな距離を置きます。

Andor は、他の Star Wars ショーから大きな距離を置きます。

アンドールの一場面。数十年前、ジョージ・ルーカスがスター・ウォーズのテレビ番組を制作するのを妨げた主な理由は、お金でした。

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、子供向けのパズルの本の序文を書き、ジョージ王子、シャーロット王女、ルイ王子と一緒にテキストを読むと述べた.

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

Yak's Produce は、数十個のつぶれたメロンを野生動物のリハビリ専門家であるレスリー グリーンと彼女のルイジアナ州の救助施設で暮らす 42 匹の動物に寄付しました。

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

8 枚目のスタジオ アルバムのリリースに向けて準備を進めているデミ ロヴァートは、「スーパー グレート ガイ」と付き合っている、と情報筋は PEOPLE に確認しています。

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

イーサン プラスの誕生日のお祝いは、TLC のウェルカム トゥ プラスビルのシーズン 4 のフィナーレで、戦争中の母親のキム プラスと妻のオリビア プラスを結びつけました。

仕事の生産性を高める 8 つのシンプルなホーム オフィスのセットアップのアイデア

仕事の生産性を高める 8 つのシンプルなホーム オフィスのセットアップのアイデア

ホームオフィスのセットアップ術を極めよう!AppExert の開発者は、家族全員が一緒にいる場合でも、在宅勤務の技術を習得しています。祖父や曽祖父が共同家族で暮らしていた頃の記憶がよみがえりました。

2022 年、私たちのデジタル ライフはどこで終わり、「リアル ライフ」はどこから始まるのでしょうか?

20 年前のタイムトラベラーでさえ、日常生活におけるデジタルおよびインターネットベースのサービスの重要性に驚くことでしょう。MySpace、eBay、Napster などのプラットフォームは、高速化に焦点を合わせた世界がどのようなものになるかを示してくれました。

ニューロマーケティングの秘密科学

ニューロマーケティングの秘密科学

マーケティング担当者が人間の欲望を操作するために使用する、最先端の (気味が悪いと言う人もいます) メソッドを探ります。カートをいっぱいにして 3 桁の領収書を持って店を出る前に、ほんの数点の商品を買いに行ったことはありませんか? あなたは一人じゃない。

地理情報システムの日: GIS 開発者として学ぶべき最高の技術スタック

地理情報システムの日: GIS 開発者として学ぶべき最高の技術スタック

私たちが住んでいる世界を確実に理解するには、データが必要です。ただし、空間参照がない場合、このデータは地理的コンテキストがないと役に立たなくなる可能性があります。

Language