web-dev-qa-db-ja.com

move_iteratorとは何ですか

私がそれを正しく理解していれば、a=std::move(b)は参照aをbのアドレスにバインドします。そして、この操作の後、bが指す内容は保証されません。

_move_iterator_ here の実装にはこの行があります

_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にコピーされますが、移動されません。

29
Min Lin

私がそれを正しく理解していれば、a=std::move(b)は参照abのアドレスにバインドします。そして、この操作の後、bが指す内容は保証されません。

ああ、いいえ:a必然的に参照ではありません。上記の_std::move_の使用により、コンパイラーはdecltype(a)::operator=(decltype(b)&&)が存在する場合、それを呼び出すことができます。このような代入演算子は、aへの代入中にbを保存する必要はありませんが、bは、破壊のためにsome正常な状態のままにしておく必要があります。

ただし、配列内の要素を_std::move_することは意味がないと思います。 a=std::move(b[n])の場合はどうなりますか?

それは理にかなっています...これは、各配列要素を別の変数に効率的に割り当て/移動できることを意味しますが、要素ごとに1回だけです。それらが移動元になった後、適切に記述された移動コンストラクターまたは代入演算子は、オブジェクトを有効であるが指定されていない状態のままにする必要があります。つまり、通常、オブジェクトを読み取る前に、オブジェクトを再度設定する必要があります。

私の ここに答える は、誰かが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 
_

コードは colir で実行/編集できます。

20
Tony Delroy

_move_iterator_の目的は、入力の右辺値をアルゴリズムに提供することです。

あなたの例auto a=std::move(b[n])は配列内の値を移動しませんが、配列から値を移動します。これは賢明なことです。

_std::accumulate_の秘訣は、 std :: stringのoperator + の定義です(accumulateのデフォルトバージョンは_operator+_を使用することに注意してください。右辺値引数に対して特別な最適化があります。この場合、accumulateは式_init + *begin_を使用するため、オーバーロード番号7が重要です。これにより、右側の引数のメモリが再利用されます。これが実際に最適化であることが判明した場合は、はっきりしていません。

10
pmr

http://en.cppreference.com/w/cpp/iterator/move_iterator はこれを言います:

std :: move_iteratorは、間接参照によって基になるイテレータによって返された値が右辺値に変換されることを除いて、基になるイテレータ(少なくともInputIteratorである必要があります)とまったく同じように動作するイテレータアダプタです。

範囲を受け入れ、範囲の最初から最後までイテレーターをウォークし、逆参照されたイテレーターで操作を実行するほとんどの(すべてではないにしても)標準アルゴリズム。たとえば、_std::accumulate_は次のように実装できます。

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

firstlastが通常のイテレータである場合(呼び出しは

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

の場合、_*first_は文字列への左辺値参照であり、式_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内で、firstlastは移動イテレータであるため、_*first_は右辺値の参照です。これは、_init + *first_が代わりにstd::operator+(std::string const&, std::string &&)を呼び出すことを意味します(オーバーロード7)。

9
Bulletmagnet