2015-01-20 15 views
25

Jeśli rozumiem to poprawnie, a=std::move(b) wiąże odniesienie a do adresu b. A po tej operacji zawartość, na którą wskazuje b, nie jest gwarantowana.Co to jest move_iterator dla

Realizacja move_iteratorhere ma tę linię

auto operator[](difference_type n) const -> decltype(std::move(current[n])) 
    { return std::move(current[n]); } 

Jednak nie sądzę, ma sens std::move element w tablicy. Co stanie się, jeśli a=std::move(b[n])?

Poniższy przykład myli mnie również:

std::string concat = std::accumulate(
          std::move_iterator<iter_t>(source.begin()), 
          std::move_iterator<iter_t>(source.end()), 
          std::string("1234")); 

Od concat będzie się przeznaczyć ciągły kawał pamięci do przechowywania wynik, który nie będzie miał żadnego nakładania się z source. Dane w source zostaną skopiowane do concat, ale nie zostaną przeniesione.

+3

* "Jeśli dobrze rozumiem to prawidłowe,' a = std :: posunięcie (b) '' A' wiąże odniesienie do adresu 'B'" * Tylko jeśli jest to deklaracja, a "a" jest typu referencyjnego. W przeciwnym razie jest to zadanie przeniesienia. Na przykład. jeśli 'a' i' b' są 'std :: vector ', wskaźnik do magazynu sterty, który zarządza wektorami, jest kopiowany z 'b' do' a', a 'b' zmienia się tak, by wskazywał gdzie indziej. – dyp

+1

Iteratory to abstrakcja wskaźników. Odwołanie do wskaźnika daje wartość * l *: 'int i = 42; int * p = & i; 'then' * i' jest lwartością. Podobnie dla iteratorów. Oznacza to jednak, że algorytmy zazwyczaj kopiują wartości, do których odwołują się iteratory.'move_iterator' sprawia, że' * it' zwraca wartość r. – dyp

Odpowiedz

17

Jeśli dobrze rozumiem to poprawne, a=std::move(b) wiąże się odwoływać a na adres b. A po tej operacji zawartość, na którą wskazuje b, nie jest gwarantowana.

Ach, no: a nie koniecznie jest odwołaniem. Powyższy wykorzystanie std::move również udziela pozwolenia kompilatora, aby zadzwonić decltype(a)::operator=(decltype(b)&&) jeśli istnieje: takie operatory przypisania są stosowane, gdy podczas przypisania do a wartość b nie musi być zachowany, ale b muszą być nadal pozostaje w pewnym stanu używalności dla zniszczenie.

Jednak nie sądzę, że ma to jakiś sens dla std::move elementu w tablicy. Co stanie się, jeśli a=std::move(b[n])?

Może mieć sens ... oznacza to, że poszczególne elementy tablicy mogą być efektywnie przypisane/przeniesione do innej zmiennej, ale tylko jeden raz na element.

Na przykład, jeśli miał wektor z std::string członków danych i używane idiom erase-usunięcia:

v.erase(std::remove_if(std::make_move_iterator(std::begin(v)), 
         std::make_move_iterator(std::end(v)), 
         [] (const X& x) { return ...condition...; }).base(), 
     std::end(v)); 

Powiedzmy końcowy wynik biorąc pod uwagę elementy wektora i kondycję powyżej jest to, że remove_if zagęszcza elementy zachowane z przodu v przez wykonanie v[1] = v[3];: std::string::operator=(std::string&& rhs) najprawdopodobniej zamieniają bufory (tj. zamieniają wskaźniki na początek/koniec danych tekstowych i koniec zarezerwowanej pamięci), zamiast kopiować tekst w poprzek, co może być szybsze - zwłaszcza gdy bieżący bufor v[1] jest zbyt mały, aby pomieścićWartość 210 takich, że v[1] musiałby w przeciwnym razie delete[] jego stary bufor następnie new[] większy bufor, a następnie skopiować dane tekstowe.


Aktualizacja: podane ta odpowiedź przyciągnął sporo głosów, pomyślałem, że mięso to z żywą przykład:

This full program on ideone.com ilustruje to w akcji - cesja ruch jest używany trzy razy, aby skopiować zatrzymywane nawet wartości ponad wcześniej zużytych wartości nieparzystych przez ten kod:

std::vector<X> v{2, 1, 8, 3, 4, 5, 6}; 
v.erase(std::remove_if(std::make_move_iterator(std::begin(v)), 
         std::make_move_iterator(std::end(v)), 
         [] (const X& x) { return x.n_ & 1; }).base(), 
     std::end(v)); 

z struct X z ruchem operatora przypisania drukowania "=(X&&) " i końcowy zrzut v, wyjście jest:

=(X&&) =(X&&) =(X&&) 2 8 4 6 
+0

Teraz rozumiem, myślałem ze std move, kompilator zrobiłby trochę magii, aby zamienić pamięć dwóch zmiennych lwartościowych. Okazuje się, że magia odbywa się za pomocą konstruktora kopii, który przyjmuje wartość rurn. Co oznacza, że ​​nie będzie działać na zwykłych starych typach. Na przykład, jeśli b jest tablicą int, a = std :: move (b [n]) będzie takie samo jak a = b [n]. –

+1

@MinLin: kopiowanie konstruktorów i operatorów przypisania to najbardziej oczywiste zastosowania "magii ruchu". Jak sam mówisz - nie powodują one, że operacje na "int" są bardziej wydajne - tylko typy, w których istnieje sposób przejęcia własności danych bez kopiowania lub alokacji/dealokacji. –

+0

@MinLin: Zauważ, że "konstruktor kopii, który przyjmuje wartość rvalue" jest dokładnie tym, co mamy na myśli mówiąc "konstruktor ruchu". To, co powiedziałeś, jest niecodziennym, ale perfekcyjnym sposobem na wprowadzenie go w życie. :) (Chociaż, zauważmy, że mówimy tutaj o * operatorach przydziału *, więc mógłbyś powiedzieć "operator przydziału kopiowania, który bierze wartość" lub "operator przydziału przeniesienia" - znowu oba idealnie dokładne deskryptory.) – Quuxplusone

9

Celem move_iterator jest dostarczenie algorytmów z wartościami rali ich wejść.

Twój przykład: auto a=std::move(b[n]) nie przesuwa wartości w tablicy, ale przenosi ją z niej, co jest rozsądną rzeczą do zrobienia.

Sztuką w swojej std::accumulate jest definicja operator+ for std::string (należy pamiętać, że domyślna wersja akumuluj wykorzystuje operator+. Posiada specjalną optymalizacji dla argumentów rvalue. Dla naszego numeru przeciążeniowym Przypadek 7 jest ważne ponieważ accumulate używa wyrażenia init + *begin . to będzie starał się ponownie wykorzystać pamięć prawej strony argumentu Jeśli to faktycznie okazuje się być optymalizacja nie jest jasna

8

http://en.cppreference.com/w/cpp/iterator/move_iterator mówi w ten sposób:..

std :: move_iterator jest iterator Ada ptor, który zachowuje się dokładnie tak jak iterator będący podstawą (który musi być przynajmniej InputIterator), z wyjątkiem tego, że dereferencje konwertują wartość zwróconą przez iterator będący podstawą do wartości r.

Większość (jeśli nie wszystkie) standardowe algorytmy, które akceptują zakres, idą od początku iteratora od początku zakresu do końca i wykonują operację na iteratorze dereferencyjnym. Na przykład, std::accumulate może być realizowane jako:

template <class InputIterator, class T> 
T accumulate (InputIterator first, InputIterator last, T init) 
{ 
    while (first!=last) { 
    init = init + *first; 
    ++first; 
    } 
    return init; 
} 

Jeśli first i last są normalnymi iteratory (rozmowa była

std::accumulate(source.begin(), source.end(), std::string("1234")); 

, następnie *first jest lwartością odniesienie do łańcucha, a wyrażenie init + *first wezwie std::operator+(std::string const&, std::string const&) (przeciążenie 1 here).

Jednakże, jeśli połączenie miało numer

std::accumulate(std::make_move_iterator(source.begin()), std::make_move_iterator(source.end()), std::string("1234")); 

następnie wewnątrz std :: akumuluj first i last są iteratory ruch, a tym samym *first jest odniesienie RValue.Oznacza to, że zamiast tego init + *first wywołuje (przeciążenie 7).

+0

W rzeczywistości przeciążenie 7 (gdzie lhs jest lwartością [string const &] i rhs jest rwartością [string &&]) jest zaimplementowane w następujący sposób: 'return std :: move (rhs.insert (0, lhs));' in aby uniknąć konieczności kopiowania (const) lhs przed modyfikacją. Zamiast tego modyfikuje (tymczasowy) rhs, a następnie kradnie z niego. – Bulletmagnet

+0

Przeciążenie 8 (gdzie oba łańcuchy są tymczasowymi wartościami r) jest jeszcze bardziej inteligentne: wstawia lhs do rhs lub dodaje rhs do lhs, w zależności od tego, który łańcuch ma wystarczającą pojemność dla całego wyniku. – Bulletmagnet