[解決済み] なぜ `std::move` という名前なのですか?
疑問点
C++11の
std::move(x)
関数は実際にはまったく何も動かしません。 単に r-value にキャストしているだけです。 なぜこのようなことが行われたのでしょうか? これは誤解を招くものではないでしょうか?
どのように解決するのですか?
正しいのは
std::move(x)
は単にrvalueへのキャストであり、より具体的には
xvalue
とは対照的に
値
. という名前のキャストを持つことも事実です。
move
という名前のキャストを持つことが、時に人々を混乱させることも事実です。 しかし、この命名の意図は混乱させることではなく、むしろコードをより読みやすくすることです。
の歴史は
move
にさかのぼります。
の移動提案までさかのぼります。
. この論文では、まず rvalue リファレンスを紹介し、より効率的な
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);
}
歴史のこの時点では、"だけだったことを思い起こす必要があります。
&&
を意味する可能性があるのは
論理的で
. 誰も rvalue の参照に精通しておらず、lvalue を rvalue にキャストすることの意味もわかっていませんでした (ただし、コピーを作成するのではなく
static_cast<T>(t)
のようにコピーはしないが)。 ですから、このコードの読者は当然こう思うでしょう。
私はどのように
swap
がどのように機能するか(temporaryにコピーして値を交換する)はわかっていますが、あの醜いキャストは何のためなのでしょうか?
また、以下の点にも注意してください。
swap
は実際にはあらゆる種類の順列変更アルゴリズムの代用品に過ぎないことにも注意してください。 この議論は
大いに
よりもはるかに大きなものです。
swap
.
次に、この提案では
シンタックスシュガー
を導入します。
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 と移動セマンティックスの間の密接な関係を本当に理解していた人はほとんどいなかったことを理解する必要があります (ただし、この論文ではその点についても説明しようとしています)。
引数として rvalue が与えられると、着手セマンティクスが自動的に適用されます。 引数が与えられると、移動セマンティクスが自動的に適用されます。のリソースを移動してもプログラムの他の部分には気づかれないので、これは完全に安全です。 rvalue からリソースを移動しても、プログラムの残りの部分には気づかれないからです ( は他の誰も を検出するためのrvalueへの参照を持っていません。 ).
もし、その時に
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
を使用していたため、誰も質問しませんでした。
でも、なんで引っ越すの?
年月が経ち、提案が洗練されるにつれて、lvalue と rvalue という概念は 値のカテゴリ に改良されました。
(画像は恥ずかしながら dirkgently )
で、今日、もし私たちが
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 から
コピー
を正確に理解していなくても、右辺から
どのように
を正確に理解していなくてもです。
さらに、プログラマがこの機能を別の名前で望む場合。
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
を rvalue にキャストし、その rvalue を
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
! 実行時には完全に消えてしまいます。 唯一の影響はコンパイル時で、それは
があるかもしれません。
どのオーバーロードが呼ばれるかを変更します。
関連
-
[解決済み】演算子のオーバーロード C++; <<操作のパラメータが多すぎる
-
[解決済み] using namespace std;」はなぜバッドプラクティスだと言われるのですか?
-
[解決済み] なぜC++はPythonよりもstdinからの行の読み込みが遅いのですか?
-
[解決済み] なぜテンプレートはヘッダーファイルでしか実装できないのですか?
-
[解決済み] ムーブセマンティクスとは何ですか?
-
[解決済み] なぜ、オブジェクトそのものではなく、ポインタを使用しなければならないのですか?
-
[解決済み] std::move()とは何ですか?また、どのような場合に使用するのですか?
-
[解決済み] C++11 rvalues と移動セマンティクスの混乱(return 文)
-
[解決済み】ローカル変数のメモリはスコープ外からアクセスできる?
-
[解決済み】なぜC++プログラマは'new'の使用を最小限に抑えなければならないのでしょうか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】C++でint型に無限大を設定する
-
[解決済み】LLVMで暗黙のうちに削除されたコピーコンストラクタの呼び出し
-
[解決済み】Cygwin Make bash コマンドが見つかりません。
-
[解決済み] 非常に基本的なC++プログラムの問題 - バイナリ式への無効なオペランド
-
[解決済み】CMakeエラー at CMakeLists.txt:30 (project)。CMAKE_C_COMPILER が見つかりませんでした。
-
[解決済み] 解決済み] `pthread_create' への未定義の参照 [重複] [重複
-
[解決済み】なぜ、サイズ8の初期化されていない値を使用するのでしょうか?
-
[解決済み] to_string は std のメンバーではない、と g++ が言っている (mingw)
-
[解決済み】演算子のオーバーロード C++; <<操作のパラメータが多すぎる
-
[解決済み】エラー。引数リストに一致するコンストラクタのインスタンスがない