これは正しいです。これは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::move
はj
右辺値にキャストするだけで、その右辺値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
!実行時に完全に消えます。その唯一の影響はコンパイル時に発生し、呼び出されるオーバーロードが変更される可能性があります。
std::move
、実際に移動...