Что такое итератор перемещения для

если я правильно понял,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 но не переехал.

3 ответов


если я правильно понял,a=std::move(b) персонализация ссылка a в адрес b. И после этой операции содержимое, на которое указывает b, не гарантируется.

Ах, нет: a - это не обязательно ссылка. Выше использование std::move также предоставляет компилятору разрешение на вызов decltype(a)::operator=(decltype(b)&&) если он существует: такие операторы присваивания используются при назначении a значение b не нужно сохранять, но!--4--> должен быть оставлен в некоторые вменяемое состояние для уничтожения.

однако, я не думаю, что это имеет смысл std::move элемент в массиве. Что произойдет, если a=std::move(b[n])?

это может иметь смысл... это просто означает, что каждый элемент массива может быть эффективно назначен/перемещен в другую переменную, но только один раз на элемент. После того, как они были перемещены, правильно написанный конструктор перемещения или оператор присваивания должны уйти объекты в допустимом, но неопределенном состоянии, что означает, что вы обычно хотите установить их снова перед чтением из них.

мой ответ тут показывает, как кто-то может добавлять/перемещать элементы из list до vector. С текущими стандартами C++ вы можете создавать move_iterators прямо так.

приведенный ниже код показывает, как-даже со старыми компиляторами / стандартами C++ -make_move_iterator можно использовать с std::copy если вы хотите перейти от элементов в диапазон итераторов источника.

#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';
}

выход:

2 1 8 3 4 5 6 
=(X&&) =(X&&) =(X&&) =(X&&) =(X&&) 2 1 -8 -3 -4 -5 -6 

код может быть запущен / ред. от coliru.


цель move_iterator является предоставление алгоритмов с rvalues их входов.

ваш пример auto a=std::move(b[n]) не перемещает значение в массиве, а перемещает его из него,что является разумной вещью.

фишка в std::accumulate определение оператор+ для std:: string (помните, что версия accumulate по умолчанию использует operator+. Он имеет специальную оптимизацию для Аргументов rvalue. Для нашего случая перегрузка № 7 является важной с accumulate использует выражение init + *begin. Это попытается повторно использовать память аргумента правой стороны. Если это на самом деле оказывается оптимизацией, это не совсем понятно.


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:: аккумулировать,first и last есть двигаться итераторы и поэтому *first - это rvalue ссылку. Это значит, что init + *first звонки std::operator+(std::string const&, std::string &&) вместо (перегрузка 7).