Benar bahwa std::move(x)
itu hanya gips untuk menilai ulang - lebih khusus untuk xvalue , yang bertentangan dengan prvalue . Dan memang benar bahwa memiliki pemain bernama move
kadang-kadang membingungkan orang. Namun maksud penamaan ini bukan untuk membingungkan, melainkan untuk membuat kode Anda lebih mudah dibaca.
Sejarah move
tanggal kembali ke proposal pindah asli pada tahun 2002 . Makalah ini pertama kali memperkenalkan referensi nilai, dan kemudian menunjukkan cara menulis yang lebih efisien 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);
}
Kita harus ingat bahwa pada titik ini dalam sejarah, satu - satunya hal yang &&
mungkin bisa berarti adalah logis dan . Tidak ada yang akrab dengan referensi nilai, atau implikasi dari melemparkan nilai ke nilai (sementara tidak membuat salinan seperti yang static_cast<T>(t)
akan dilakukan). Jadi pembaca kode ini secara alami akan berpikir:
Saya tahu bagaimana swap
seharusnya bekerja (menyalin ke sementara dan kemudian bertukar nilai), tetapi apa tujuan dari para pemain jelek itu ?!
Perhatikan juga bahwa swap
ini hanyalah stand-in untuk semua jenis algoritma permutasi-modifikasi. Diskusi ini jauh , jauh lebih besar daripada swap
.
Kemudian proposal tersebut memperkenalkan sintaksis gula yang menggantikannya static_cast<T&&>
dengan sesuatu yang lebih mudah dibaca yang menyampaikan tidak tepat apa , melainkan mengapa :
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
Yaitu move
hanya untuk sintaksis static_cast<T&&>
, dan sekarang kodenya cukup sugestif mengenai mengapa para pemain itu ada di sana: untuk memungkinkan pemindahan semantik!
Orang harus memahami bahwa dalam konteks sejarah, hanya sedikit orang pada saat ini yang benar-benar memahami hubungan intim antara nilai-nilai dan memindahkan semantik (walaupun makalah ini mencoba menjelaskannya juga):
Pindah semantik akan secara otomatis ikut bermain ketika diberikan argumen nilai. Ini sangat aman karena memindahkan sumber daya dari suatu nilai tidak dapat diketahui oleh seluruh program ( tidak ada orang lain yang memiliki referensi ke nilai untuk mendeteksi perbedaan ).
Jika pada saat swap
itu disajikan seperti ini:
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);
}
Maka orang akan melihat itu dan berkata:
Tapi mengapa Anda memilih untuk menilai?
Poin utama:
Seperti itu, menggunakan move
, tidak ada yang pernah bertanya:
Tapi mengapa kamu pindah?
Seiring berlalunya waktu dan proposal itu disempurnakan, gagasan nilai dan nilai diubah menjadi kategori nilai yang kita miliki saat ini:
(gambar tanpa malu-malu dicuri dari dirkgently )
Dan hari ini, jika kita ingin swap
mengatakan apa yang dilakukannya, daripada mengapa , itu akan terlihat seperti:
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);
}
Dan pertanyaan yang harus ditanyakan oleh setiap orang kepada diri mereka sendiri adalah apakah kode di atas lebih atau kurang dapat dibaca daripada:
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
Atau bahkan yang asli:
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);
}
Dalam hal apa pun, pembuat program C ++ pekerja harian harus tahu bahwa di balik tudung move
, tidak ada yang lebih dari sekadar pemeran. Dan programmer C ++ pemula, setidaknya dengan move
, akan diberitahu bahwa tujuannya adalah untuk pindah dari rhs, sebagai lawan menyalin dari rhs, bahkan jika mereka tidak mengerti persis bagaimana hal itu dilakukan.
Selain itu, jika seorang programmer menginginkan fungsi ini dengan nama lain, std::move
tidak memiliki monopoli pada fungsi ini, dan tidak ada keajaiban bahasa non-portabel yang terlibat dalam implementasinya. Sebagai contoh jika seseorang ingin kode set_value_category_to_xvalue
, dan menggunakannya sebagai gantinya, itu sepele untuk melakukannya:
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);
}
Dalam C ++ 14 itu menjadi lebih ringkas:
template <class T>
inline
constexpr
auto&&
set_value_category_to_xvalue(T&& t) noexcept
{
return static_cast<std::remove_reference_t<T>&&>(t);
}
Jadi, jika Anda cenderung, hiasi static_cast<T&&>
apa pun yang menurut Anda terbaik, dan mungkin akhirnya Anda akan mengembangkan praktik terbaik baru (C ++ terus berkembang).
Jadi apa yang move
dilakukan dalam hal kode objek yang dihasilkan?
Pertimbangkan ini test
:
void
test(int& i, int& j)
{
i = j;
}
Dikompilasi dengan clang++ -std=c++14 test.cpp -O3 -S
, ini menghasilkan kode objek ini:
__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
Sekarang jika tes diubah menjadi:
void
test(int& i, int& j)
{
i = std::move(j);
}
Sama sekali tidak ada perubahan sama sekali dalam kode objek. Seseorang dapat menggeneralisasi hasil ini ke: Untuk objek yang bergerak sepele , std::move
tidak memiliki dampak.
Sekarang mari kita lihat contoh ini:
struct X
{
X& operator=(const X&);
};
void
test(X& i, X& j)
{
i = j;
}
Ini menghasilkan:
__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
Jika Anda menjalankan __ZN1XaSERKS_
melalui c++filt
menghasilkan: X::operator=(X const&)
. Tidak mengherankan di sini. Sekarang jika tes diubah menjadi:
void
test(X& i, X& j)
{
i = std::move(j);
}
Kemudian masih tidak ada perubahan apa pun dalam kode objek yang dihasilkan. std::move
tidak melakukan apa pun selain melemparkan j
ke nilai sebuah, dan kemudian nilai itu X
mengikat ke operator penugasan salinan X
.
Sekarang mari tambahkan operator pemindahan tugas ke X
:
struct X
{
X& operator=(const X&);
X& operator=(X&&);
};
Sekarang kode objek tidak berubah:
__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
Menjalankan __ZN1XaSEOS_
melalui c++filt
mengungkapkan bahwa X::operator=(X&&)
yang dipanggil bukannya X::operator=(X const&)
.
Dan hanya itu yang ada untuk std::move
! Ini benar-benar menghilang pada saat dijalankan. Satu-satunya dampaknya adalah pada saat kompilasi di mana ia dapat mengubah apa yang disebut overload.
std::move
benar - benar bergerak ..