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.