Bunun std::move(x)
sadece rvalue'ya dönüştürme olduğu doğrudur - daha spesifik olarak bir prvalue yerine bir xvalue için . Ayrıca bir oyuncu kadrosuna sahip olmanın move
bazen insanların kafasını karıştırdığı da doğrudur . Ancak bu isimlendirmenin amacı kafa karıştırmak değil, kodunuzu daha okunaklı hale getirmektir.
Tarihi, 2002'deki orijinal taşınma önerisinemove
kadar uzanıyor . Bu makale önce rvalue referansını tanıtıyor ve daha sonra nasıl daha verimli yazılacağını gösteriyor :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);
}
Tarihin bu noktasında, " &&
" nin muhtemelen " " anlamına gelebilecek tek şeyin mantıksal ve mantıklı olduğunu hatırlamak gerekir . Hiç kimse rvalue referanslarına aşina değildi, ne de bir rvalue için bir lvalue atmanın sonuçlarına aşina değildi (ama yapacağı gibi bir kopya oluşturmazken static_cast<T>(t)
). Dolayısıyla, bu kodun okuyucuları doğal olarak şunları düşünür:
Nasıl swap
çalışacağımı biliyorum (geçiciye kopyalayın ve sonra değerleri değiştirin), ama bu çirkin yayınların amacı nedir ?!
Ayrıca bunun swap
her türlü permütasyon değiştirme algoritması için gerçekten sadece bir stand-in olduğunu unutmayın . Bu tartışma çok , çok daha büyük swap
.
Daha sonra teklif , tam olarak neyi değil, daha çok nedenini ileten daha okunabilir bir şeyle değiştiren sözdizimi şekeri sunar :static_cast<T&&>
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
Yani move
, sadece sözdizimi şekeri static_cast<T&&>
ve şimdi kod, bu yayınların neden orada olduğuna dair oldukça fikir veriyor: hareket anlamını etkinleştirmek için!
Tarih bağlamında, bu noktada çok az insanın rdeğerler ile hareket semantikleri arasındaki yakın bağlantıyı gerçekten anladığını anlamak gerekir (yine de makale bunu açıklamaya çalışır):
Rvalue bağımsız değişkenleri verildiğinde, hareket semantiği otomatik olarak devreye girecektir. Bu tamamen güvenlidir, çünkü kaynakların bir r değerinden taşınması programın geri kalanı tarafından fark edilemez ( başka hiç kimse bir farkı saptamak için r değerine başvurmaz ).
O zaman swap
bunun yerine şu şekilde sunulduysa:
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);
}
O zaman insanlar buna bakıp şöyle derdi:
Ama neden rvalue için yayın yapıyorsunuz?
Ana nokta:
Olduğu gibi, kullanarak move
, hiç kimse sormadı:
Ama neden hareket ediyorsun?
Yıllar geçtikçe ve teklif geliştirildikçe, lvalue ve rvalue kavramları bugün sahip olduğumuz değer kategorilerinde rafine edildi :
(Resim shamelessly çalınan dirkgently )
Ve bugün, neden yerine swap
tam olarak ne yaptığını söylemek istersek , daha çok şöyle görünmeli:
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);
}
Ve herkesin kendine sorması gereken soru, yukarıdaki kodun aşağıdakilerden daha fazla veya daha az okunabilir olup olmadığıdır:
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
Veya orijinal bile:
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);
}
Her halükarda, kalfalık C ++ programcısı bilmeli ki, başlık altında, bir alçıdan move
başka bir şey olmuyor. Ve başlangıç seviyesindeki C ++ programcısına, en azından move
, tam olarak anlamasalar bile , niyetin rhs'den kopyalamak yerine, rhs'den geçmek olduğu konusunda bilgilendirilecektir. nasıl başarıldığını .
Ek olarak, bir programcı bu işlevselliği başka bir isim altında arzu ederse std::move
, bu işlevsellik üzerinde tekele sahip olmazsa ve bunun uygulanmasında taşınabilir olmayan dil büyüsü yoktur. Örneğin, biri kodlamak set_value_category_to_xvalue
ve onun yerine onu kullanmak isterse, bunu yapmak önemsizdir:
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'te daha da özlü hale gelir:
template <class T>
inline
constexpr
auto&&
set_value_category_to_xvalue(T&& t) noexcept
{
return static_cast<std::remove_reference_t<T>&&>(t);
}
Öyleyse, bu kadar eğilimliysen, static_cast<T&&>
en iyi olduğunu düşündüğün şekilde dekore et ve sonunda yeni bir en iyi uygulama geliştireceksin (C ++ sürekli gelişiyor).
Peki move
üretilen nesne kodu açısından ne yapar ?
Şunu bir düşünün test
:
void
test(int& i, int& j)
{
i = j;
}
İle derlendiğinde clang++ -std=c++14 test.cpp -O3 -S
, bu nesne kodunu üretir:
__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
Şimdi test şu şekilde değiştirilirse:
void
test(int& i, int& j)
{
i = std::move(j);
}
Orada hiç bir değişiklik kesinlikle nesne kodunda. Bu sonuç şu şekilde genelleştirilebilir: Önemsiz şekilde hareketli nesneler için std::move
etkisi yoktur.
Şimdi şu örneğe bakalım:
struct X
{
X& operator=(const X&);
};
void
test(X& i, X& j)
{
i = j;
}
Bu şunları oluşturur:
__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
Eğer çalıştırırsanız __ZN1XaSERKS_
aracılığıyla c++filt
ürettiği: X::operator=(X const&)
. Burada sürpriz yok. Şimdi test şu şekilde değiştirilirse:
void
test(X& i, X& j)
{
i = std::move(j);
}
O zaman üretilen nesne kodunda herhangi bir değişiklik olmaz . bir rvalue'ya çevirmekten std::move
başka hiçbir şey yapmadı j
ve sonra bu rvalue, öğesinin X
kopya atama operatörüne bağlanır X
.
Şimdi şuna bir taşıma atama operatörü ekleyelim X
:
struct X
{
X& operator=(const X&);
X& operator=(X&&);
};
Şimdi nesne kodu yapar değişikliği:
__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
Koşu __ZN1XaSEOS_
yoluyla c++filt
ortaya koymaktadırX::operator=(X&&)
yerine çağrılan X::operator=(X const&)
.
Ve işte orada hepsi std::move
! Çalışma zamanında tamamen kaybolur. Onun tek darbe bu derleme zamanda olabilir aşırı yük çağrılan yapmanıza neden.
std::move