Подтвердить что ты не робот

Что такое 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, но не будут перемещены.

4b9b3361

Ответ 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).