c++ - tag - Para qué es move_iterator
etiqueta de titulo en html (3)
Si lo comprendo correctamente,
a=std::move(b)
enlaza la referencia a la dirección deb
. Y después de esta operación, el contenido que apunta no está garantizado.
Ah, no: a
no es necesariamente una referencia. El uso anterior de std::move
también otorga permiso al compilador para llamar a decltype(a)::operator=(decltype(b)&&)
si existe: tales operadores de asignación se usan cuando durante la asignación a a
el valor de b
necesario no debe conservarse, pero aún debe dejarse en algún estado sano para su destrucción.
Sin embargo, no creo que tenga sentido
std::move
un elemento en una matriz. ¿Qué sucede sia=std::move(b[n])
?
Puede tener sentido ... solo significa que cada elemento de la matriz se puede asignar / mover de manera eficiente a otra variable, pero solo una vez por elemento. Después de que se hayan movido de, un constructor de movimientos u operador de asignación correctamente escrito debe dejar los objetos en un estado válido pero no especificado, lo que significa que normalmente querrá configurarlos nuevamente antes de leerlos.
Mi respuesta aquí muestra cómo alguien podría agregar / mover elementos de una list
a un vector
. Con los estándares actuales de C ++, puedes crear move_iterators directamente así.
El siguiente código muestra cómo, incluso con compiladores más antiguos / estándares de C ++, make_move_iterator
se puede usar con std::copy
si desea moverse desde los elementos en el rango del iterador de origen.
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
struct X
{
X(int n) : n_(n) { }
X(const X& rhs) : n_(rhs.n_) { }
X(X&& rhs) : n_{ rhs.n_ } { rhs.n_ *= -1; std::cout << "=(X&&) "; }
X& operator=(X&& rhs) { n_ = rhs.n_; rhs.n_ *= -1; std::cout << "=(X&&) "; return *this; }
int n_;
};
int main()
{
std::vector<X> v{2, 1, 8, 3, 4, 5, 6};
std::vector<X> v2{};
std::copy(v.begin() + 2, v.end(), std::insert_iterator(v2, v2.end()));
for (auto& x : v)
std::cout << x.n_ << '' '';
std::cout << ''/n'';
std::copy(std::make_move_iterator(v.begin() + 2), std::make_move_iterator(v.end()), std::insert_iterator(v2, v2.end()));
for (auto& x : v)
std::cout << x.n_ << '' '';
std::cout << ''/n'';
}
Salida:
2 1 8 3 4 5 6
=(X&&) =(X&&) =(X&&) =(X&&) =(X&&) 2 1 -8 -3 -4 -5 -6
El código se puede ejecutar / editar en coliru .
Si lo comprendo correctamente, a=std::move(b)
enlaza la referencia a la dirección de b. Y después de esta operación, el contenido que apunta no está garantizado.
La implementación de move_iterator
here tiene esta línea.
auto operator[](difference_type n) const -> decltype(std::move(current[n]))
{ return std::move(current[n]); }
Sin embargo, no creo que tenga sentido std::move
un elemento en una matriz. ¿Qué sucede si a=std::move(b[n])
?
El siguiente ejemplo también me confunde:
std::string concat = std::accumulate(
std::move_iterator<iter_t>(source.begin()),
std::move_iterator<iter_t>(source.end()),
std::string("1234"));
Dado que el concat
asignará una porción continua de memoria para almacenar el resultado, que no tendrá ninguna superposición con la source
. Los datos en la source
se copiarán a concat
pero no se concat
.
El propósito de move_iterator
es proporcionar algoritmos con valores de sus entradas.
Su ejemplo auto a=std::move(b[n])
no mueve un valor en una matriz, sino que lo saca de ella, lo cual es una cosa sensata.
El truco en std::accumulate
es la definición de operador + para cadena estándar (recuerde que la versión predeterminada de acumulación utiliza operator+
. Tiene una optimización especial para los argumentos de valor. Para nuestro caso, la sobrecarga número 7 es la más importante desde que se accumulate
usa la expresión init + *begin
. Esto intentará reutilizar la memoria del argumento del lado derecho. Si esto realmente resulta ser una optimización, no está realmente claro.
http://en.cppreference.com/w/cpp/iterator/move_iterator dice esto:
std :: move_iterator es un adaptador de iterador que se comporta exactamente igual que el iterador subyacente (que debe ser al menos un InputIterator), excepto que la desreferenciación convierte el valor devuelto por el iterador subyacente en un valor r.
La mayoría (si no todos) de los algoritmos estándar que aceptan un rango, recorren un iterador desde el principio hasta el final y realizan una operación en el iterador sin referencia. Por ejemplo, std::accumulate
puede implementarse como:
template <class InputIterator, class T>
T accumulate (InputIterator first, InputIterator last, T init)
{
while (first!=last) {
init = init + *first;
++first;
}
return init;
}
Si el first
y el last
son iteradores normales (la llamada fue
std::accumulate(source.begin(), source.end(), std::string("1234"));
, luego *first
es una referencia lvalue a cadena, y la expresión init + *first
llamará std::operator+(std::string const&, std::string const&)
(sobrecarga 1 aquí ).
Sin embargo, si la llamada era
std::accumulate(std::make_move_iterator(source.begin()), std::make_move_iterator(source.end()), std::string("1234"));
luego dentro de std :: acumule, el first
y el last
son los iteradores de movimiento y, por lo tanto, *first
es una referencia de valor. Esto significa que init + *first
llama a std::operator+(std::string const&, std::string &&)
lugar (sobrecarga 7).