Что такое move_iterator для
Если я правильно понимаю, a=std::move(b)
связывает ссылку a с адресом b. И после этой операции контент, на который указывает b, не гарантируется.
Реализация move_iterator
здесь имеет эту строку
auto operator[](difference_type n) const -> decltype(std::move(current[n]))
{ return std::move(current[n]); }
Однако, я не думаю, что имеет смысл std::move
элемент в массиве. Что произойдет, если a=std::move(b[n])
?
Следующий пример меня смущает:
std::string concat = std::accumulate(
std::move_iterator<iter_t>(source.begin()),
std::move_iterator<iter_t>(source.end()),
std::string("1234"));
Поскольку concat
сам будет выделять непрерывный кусок памяти для хранения результата, который не будет перекрываться с source
. Данные в source
будут скопированы на concat
, но не будут перемещены.
Ответы
Ответ 1
Если я правильно понимаю, a=std::move(b)
связывает ссылку a
с адресом b
. И после этой операции контент, на который указывает b, не гарантируется.
Ah, no: a
не обязательно является ссылкой. Вышеприведенное использование std::move
также предоставляет разрешение компилятора для вызова decltype(a)::operator=(decltype(b)&&)
, если оно существует: такие операторы присваивания используются, когда во время присвоения a
значение b
не обязательно должно сохраняться, но b
должно все же останутся в каком-то нормальном состоянии для уничтожения.
Однако я не думаю, что имеет смысл std::move
элемент в массиве. Что произойдет, если a=std::move(b[n])
?
Это может иметь смысл... это просто означает, что каждый элемент массива может быть эффективно назначен/перенесен на другую переменную, но только один раз на элемент.
Например, если у вас был вектор с элементами std::string
и использовался идиома erase-remove:
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));
Скажем, конечный результат, приведенный выше, состоит в том, что remove_if
сжимает элементы, удерживаемые в начале v
, выполняя v[1] = v[3];
: std::string::operator=(std::string&& rhs)
, скорее всего, заменит буферы (т.е. поменяйте указатели на начало/конец текстовых данных и конец зарезервированной памяти), а не скопируйте текст поперек, что может быть быстрее - особенно если v[1]
текущий буфер слишком мал, чтобы удерживать значение v[3]
таким образом, что v[1]
иначе потребовалось бы delete[]
его старый буфер, а затем new[]
больший буфер, а затем скопировать текстовые данные.
Обновление: учитывая, что этот ответ привлек немало голосов, подумал, что я воспользуюсь живым примером:
Эта полная программа на ideone.com иллюстрирует это в действии - назначение переноса используется три раза, чтобы скопировать сохраненные четные значения по сравнению с ранее отброшенными нечетными значения по этому коду:
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));
С помощью struct X
с оператором присваивания перемещения "=(X&&) "
и окончательным дампом v
вывод:
=(X&&) =(X&&) =(X&&) 2 8 4 6
Ответ 2
Цель move_iterator
заключается в предоставлении алгоритмов с r значениями их входов.
Ваш пример auto a=std::move(b[n])
не перемещает значение в массиве, а выводит его из него, что разумно.
Тройка в вашем std::accumulate
- это определение operator + для std::string (помните, что по умолчанию используется функция накапливания operator+
. оптимизация для аргументов rvalue. Для нашего случая число перегрузки 7 является важным, поскольку accumulate
использует выражение init + *begin
. Это попытается повторно использовать память аргумента правой руки. Если это фактически оказывается оптимизацией, не совсем понятно.
Ответ 3
http://en.cppreference.com/w/cpp/iterator/move_iterator говорит следующее:
std:: move_iterator - это итератор-адаптер, который ведет себя точно так же, как и нижний итератор (который должен быть как минимум InputIterator), за исключением того, что разыменование преобразует значение, возвращаемое базовым итератором, в rvalue.
Большинство (если не все) стандартных алгоритмов, которые принимают диапазон, проходят итератор с начала диапазона до конца и выполняют операцию с разыменованным итератором. Например, std::accumulate
может быть реализовано как:
template <class InputIterator, class T>
T accumulate (InputIterator first, InputIterator last, T init)
{
while (first!=last) {
init = init + *first;
++first;
}
return init;
}
Если first
и last
являются нормальными итераторами (вызов был
std::accumulate(source.begin(), source.end(), std::string("1234"));
тогда *first
является ссылкой lvalue на строку, а выражение init + *first
вызывает std::operator+(std::string const&, std::string const&)
(перегрузка 1 здесь).
Однако, если вызов был
std::accumulate(std::make_move_iterator(source.begin()), std::make_move_iterator(source.end()), std::string("1234"));
тогда внутри std:: accumulate, first
и last
- итераторы перемещения, и поэтому *first
является ссылкой rvalue. Это означает, что init + *first
вызывает std::operator+(std::string const&, std::string &&)
вместо (перегрузка 7).