Neden "std :: move", "std :: move" olarak adlandırılıyor?


129

C ++ 11 std::move(x)işlevi gerçekten hiçbir şeyi hareket ettirmez. Bu sadece r-değerine göre atılmıştır. Bu neden yapıldı? Bu yanıltıcı değil mi?


std::move
Sorunları

Ve C ++ std::char_traits::move
98/03

30
Diğer favorim, std::remove()unsurları kaldırmaması: Hala erase()bu öğeleri konteynerden çıkarmak için aramanız gerekiyor. Yani movehareket removeetmez , çıkarmaz. Adını almış olurdu mark_movable()için move.
Ali

4
@Ali Ben de mark_movable()kafa karıştırıcı bulabilirim . Aslında hiçbirinin olmadığı kalıcı bir yan etki olduğunu öne sürüyor.
finnw

Yanıtlar:


179

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 movebazen 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 swapher 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 swapbunun 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 :

Taksonomi

(Resim shamelessly çalınan dirkgently )

Ve bugün, neden yerine swaptam 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 movebaş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_xvalueve 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::moveetkisi 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::movebaşka hiçbir şey yapmadı jve sonra bu rvalue, öğesinin Xkopya 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++filtortaya 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.


7
İşte o grafik için bir nokta kaynağı: Onu digraph D { glvalue -> { lvalue; xvalue } rvalue -> { xvalue; prvalue } expression -> { glvalue; rvalue } }kamu yararına yeniden oluşturdum :) Buradan SVG olarak
sehe

7
Bu hala bisiklet düğününe açık mı? allow_move
Tavsiye

2
@dyp Benim favorim hala movable.
Daniel Frey

6
Scott Meyers yeniden adlandırma önerdi std::moveiçin rvalue_cast: youtube.com/...
nairware

6
Rvalue artık hem prvalues ​​hem de xvalues'e atıfta rvalue_castbulunduğundan, anlamında belirsizdir: Ne tür bir rvalue döndürür? xvalue_castburada tutarlı bir isim olacaktır. Ne yazık ki, şu anda çoğu insan da ne yaptığını anlamayacak. Birkaç yıl içinde açıklamam umarım yanlış olur.
Howard Hinnant

20

Burada B.Stroustrup tarafından yazılan ve OP'nin sorusuna doğrudan bir cevap olan C ++ 11 SSS'den bir alıntı bırakmama izin verin :

hareket (x), "x'i bir r değeri olarak kabul edebilirsiniz" anlamına gelir. Belki move () 'e rval () adı verilseydi daha iyi olurdu, ama şimdiye kadar move () yıllardır kullanılıyordu.

Bu arada, SSS'den gerçekten keyif aldım - okumaya değer.


2
@ HowardHinnant'ın yorumunu başka bir cevaptan intihal etmek için: Stroustrup cevabı yanlıştır, çünkü artık iki tür rdeğer vardır - prvalues ​​ve xvalues ​​ve std :: move gerçekten bir xvalue atamasıdır.
einpoklum
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.