`std :: move`が` std :: move`という名前になっているのはなぜですか?


128

C ++ 11 std::move(x)関数は実際には何も動かしません。これは、r値への単なるキャストです。なぜこれが行われたのですか?これは紛らわしいのではないですか?


3引数、さらに悪いことにstd::move、実際に移動...
Cubbi

そして、C ++ std::char_traits::move
98/03/11を

30
私のもう1つのお気に入りはstd::remove()、要素を削除しないことです。それでもerase()、実際にそれらの要素をコンテナーから削除するために呼び出す必要があります。したがってmove、移動したり、remove削除したりしません。の名前mark_movable()を選んだでしょうmove
Ali

4
@アリmark_movable()も混乱するでしょう。それは実際にはない永続的な副作用があることを示唆しています。
finnw

回答:


177

これは正しいです。これはstd::move(x)、右辺値へのキャストです。具体的には、右辺値ではなく、xへのキャストです。また、キャストを指名するmoveことで時々人々を混乱させることも事実です。ただし、この命名の目的は混乱させることではなく、コードを読みやすくすることです。

move日付の歴史は、2002年の最初の移転提案にさかのぼります。このホワイトペーパーでは、最初に右辺値参照を紹介し、次により効率的な記述方法を示しますstd::swap

template <class T>
void
swap(T& a, T& b)
{
    T tmp(static_cast<T&&>(a));
    a = static_cast<T&&>(b);
    b = static_cast<T&&>(tmp);
}

一つは、リコールに持っている歴史の中で、この時点では、唯一のことは「ということを&&」おそらく平均した可能性が論理的で。右辺値の参照や、左辺値を右辺値にキャストすることの影響については誰も理解していませんでした(コピーを作成するのでstatic_cast<T>(t)はありません)。したがって、このコードの読者は自然に考えます。

私はどのように機能するswapはずかを知っていますが(一時ファイルにコピーしてから値を交換します)、これらの醜いキャストの目的は何ですか?

また、これswapは実際にはあらゆる種類の順列変更アルゴリズムの代わりにすぎないことに注意してください。この議論ははるかに、はるかに大きいですswap

次に、この提案では、を正確なwhatではなく、その理由を伝えるより読みやすいものに置き換える構文糖を紹介しますstatic_cast<T&&>

template <class T>
void
swap(T& a, T& b)
{
    T tmp(move(a));
    a = move(b);
    b = move(tmp);
}

すなわちmoveのためだけの構文糖であるstatic_cast<T&&>、と今のコードは、これらのキャストがある理由としては非常に示唆的である:移動セマンティクスを有効にするために!

歴史の文脈では、現時点では、rvaluesとmoveのセマンティクスの間の密接な関係を実際に理解している人はほとんどいないことを理解する必要があります(ただし、この論文ではそれについても説明しています)。

右辺値引数が指定されると、移動セマンティクスが自動的に機能します。右辺値からのリソースの移動はプログラムの残りの部分では気付かないので、これは完全に安全です違いを検出するために右辺値を参照する人は誰もいません)。

当時swapは代わりに次のように提示された場合:

template <class T>
void
swap(T& a, T& b)
{
    T tmp(cast_to_rvalue(a));
    a = cast_to_rvalue(b);
    b = cast_to_rvalue(tmp);
}

それから人々はそれを見て言ったでしょう:

しかし、なぜrvalueにキャストするのですか?


主なポイント:

実際、を使用してmove、誰も尋ねたことはありませんでした。

しかし、なぜあなたは動いているのですか?


年月が経過し、提案が洗練されたので、左辺値と右辺値の概念は、今日の値カテゴリに洗練されました。

分類

恥ずかしがらずに盗まれた画像)

そして今日、なぜそれをしているのではなく、それが何をしているのswapを正確に述べたい場合、それは次のようになります:

template <class T>
void
swap(T& a, T& b)
{
    T tmp(set_value_category_to_xvalue(a));
    a = set_value_category_to_xvalue(b);
    b = set_value_category_to_xvalue(tmp);
}

そして、誰もが自分自身に尋ねる必要があるのは、上記のコードが多かれ少なかれ読みやすいかどうかです。

template <class T>
void
swap(T& a, T& b)
{
    T tmp(move(a));
    a = move(b);
    b = move(tmp);
}

またはオリジナル:

template <class T>
void
swap(T& a, T& b)
{
    T tmp(static_cast<T&&>(a));
    a = static_cast<T&&>(b);
    b = static_cast<T&&>(tmp);
}

いずれにせよ、ジャーニーマンC ++プログラマーは、の内部moveではキャスト以外に何も行われていないことを知っているはずです。そして、初心者のC ++プログラマは、と少なくともmove、意図がしていることが通知される移動とは反対に、RHSからコピー RHSから、彼らは正確に理解していない場合でも、どのようにそれが達成されます。

さらに、プログラマーがこの機能を別の名前で望んでいる場合、この機能をstd::move独占することはできず、その実装には移植性のない言語の魔法はありません。たとえばset_value_category_to_xvalue、をコーディングし、代わりにそれを使用したい場合、そうすることは簡単です:

template <class T>
inline
constexpr
typename std::remove_reference<T>::type&&
set_value_category_to_xvalue(T&& t) noexcept
{
    return static_cast<typename std::remove_reference<T>::type&&>(t);
}

C ++ 14ではさらに簡潔になります。

template <class T>
inline
constexpr
auto&&
set_value_category_to_xvalue(T&& t) noexcept
{
    return static_cast<std::remove_reference_t<T>&&>(t);
}

だから、もしあなたがそんなに傾向があるなら、あなたがstatic_cast<T&&>最善だと思うものを飾ってください、そしておそらくあなたは新しいベストプラクティスを開発することになるでしょう(C ++は常に進化しています)。

それではmove、生成されたオブジェクトコードに関して何をするのでしょうか。

これを考慮してくださいtest

void
test(int& i, int& j)
{
    i = j;
}

でコンパイルするとclang++ -std=c++14 test.cpp -O3 -S、次のオブジェクトコードが生成されます。

__Z4testRiS_:                           ## @_Z4testRiS_
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    movl    (%rsi), %eax
    movl    %eax, (%rdi)
    popq    %rbp
    retq
    .cfi_endproc

テストが次のように変更された場合:

void
test(int& i, int& j)
{
    i = std::move(j);
}

オブジェクトコードにまったく変更はありません。この結果を一般化すると、次のようになります。ささいに動くオブジェクトの場合、std::move影響はありません。

次に、この例を見てみましょう。

struct X
{
    X& operator=(const X&);
};

void
test(X& i, X& j)
{
    i = j;
}

これは以下を生成します:

__Z4testR1XS0_:                         ## @_Z4testR1XS0_
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    popq    %rbp
    jmp __ZN1XaSERKS_           ## TAILCALL
    .cfi_endproc

実行__ZN1XaSERKS_するc++filtと、次のようになり X::operator=(X const&)ます。ここに驚きはありません。テストが次のように変更された場合:

void
test(X& i, X& j)
{
    i = std::move(j);
}

その後、生成されたオブジェクトコードに変更はありませんstd::movej右辺値にキャストするだけで、その右辺値Xはの代入代入演算子にバインドされますX

次に、移動割り当て演算子をに追加しますX

struct X
{
    X& operator=(const X&);
    X& operator=(X&&);
};

これでオブジェクトコード変更されます。

__Z4testR1XS0_:                         ## @_Z4testR1XS0_
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    popq    %rbp
    jmp __ZN1XaSEOS_            ## TAILCALL
    .cfi_endproc

を実行__ZN1XaSEOS_すると、の代わりに呼び出されてc++filtいることX::operator=(X&&)がわかりX::operator=(X const&)ます。

そして、それはだで全部std::move!実行時に完全に消えます。その唯一の影響はコンパイル時に発生し、呼び出されるオーバーロードが変更される可能性があります。


7
これがそのグラフのドットソースです。私digraph D { glvalue -> { lvalue; xvalue } rvalue -> { xvalue; prvalue } expression -> { glvalue; rvalue } }は公共の利益のために再作成しました:) ここにSVGとして
sehe

7
これはまだバイクシェディングに開放されていますか?私はお勧めしallow_moveます;)
dyp 2014年

2
@dyp私のお気に入りはまだmovableです。
Daniel Frey 2014年

6
スコット・マイヤーズは、名前の変更提案std::movervalue_castyoutube.com/...
nairware

6
現在、右辺値はprvaluesとxvaluesの両方を参照しているためrvalue_cast、その意味は曖昧です。どのような右辺値が返されるのですか? xvalue_castここでは一貫した名前になります。残念ながら、現時点では、ほとんどの人が何をしているのか理解していません。あと数年で、私の声明は間違いになるでしょう。
ハワードヒナント2014年

20

OPの質問への直接の回答であるB. Stroustrupによって書かれたC ++ 11 FAQからの引用をここに残しておきます。

move(x)は、「xを右辺値として扱うことができる」という意味です。多分move()がrval()と呼ばれた方が良かったかもしれませんが、今ではmove()が何年も使用されています。

ところで、私はFAQを本当に楽しんだ-それは読む価値がある。


2
別の回答から@HowardHinnantのコメントを盗用するには:2種類の右辺値-prvaluesとxvaluesがあり、std :: moveは実際にはxvalueキャストであるため、Stroustrupの回答は不正確です。
einpoklum
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.