72

vector への反復子がある場合、 から vectorをa移動構築または移動代入しますが、その反復子はまだ同じ要素 (現在は vector 内) を指していますか? コードでの意味は次のとおりです。bab

#include <vector>
#include <iostream>

int main(int argc, char *argv[])
{
    std::vector<int>::iterator a_iter;
    std::vector<int> b;
    {
        std::vector<int> a{1, 2, 3, 4, 5};
        a_iter = a.begin() + 2;
        b = std::move(a);
    }
    std::cout << *a_iter << std::endl; // Is a_iter valid here?
    return 0;
}

はに移動されてa_iterからまだ有効ですか、それともイテレータは移動によって無効になっていますか? 参考までに、iterators を無効にしませんabstd::vector::swap

4

4 に答える 4

26

iterators が a の後も有効であると仮定するのは合理的かもしれませんがmove、標準が実際にこれを保証しているとは思いません。したがって、イテレータは の後で未定義の状態になりmoveます。


の前に存在していたイテレータがのも有効であると具体的に述べている標準で見つけることができる参照はありません。movemove

iterator表面的には、 anは通常、制御されたシーケンスへのポインターとして実装されていると仮定することは完全に合理的であるように思われます。その場合、イテレータはmove.

しかし、の実装iteratorは実装定義です。iteratorつまり、特定のプラットフォーム上の が標準で定められた要件を満たしている限り、どのような方法でも実装できます。vector理論的には、クラスへのポインタとインデックスの組み合わせとして実装できます。その場合、イテレータは の後に無効になりますmove

iteratorが実際にこのように実装されているかどうかは関係ありません。このように実装することもできます。そのため、事後move反復子がまだ有効であるという標準からの特定の保証がなければ、それらが有効であると想定することはできません。の後のイテレータにはそのような保証があることにも注意してswapください。これは、以前の基準から具体的に明確化されました。おそらく、 a の後のイテレータについて同様の説明を行わなかったのは、Std 委員会の単なる見落としでしたmoveが、いずれにせよ、そのような保証はありません。

したがって、長いことも短いことも、イテレータがmove.

編集:

ドラフト n3242 の 23.2.1/11 には、次のように記載されています。

別の方法で指定されていない限り (明示的に、または他の関数に関して関数を定義することによって)、コンテナー メンバー関数を呼び出したり、コンテナーをライブラリー関数への引数として渡したりしても、そのコンテナー内のオブジェクトの反復子を無効にしたり、その値を変更したりしてはなりません。 .

これにより、イテレータは a の後に有効であると結論付けられるかもしれませんmoveが、私は同意しません。あなたのコード例でa_iterは、へのイテレータでしたvector a。の後move、そのコンテナaは確かに変更されています。私の結論は、この場合、上記の条項は適用されないということです。

于 2012-06-13T20:00:37.920 に答える
12

移動の構成を移動の割り当てに変更した編集により、答えが変わると思います。

少なくとも私が表 96 を正しく読んでいれば、ムーブ構築の複雑さは「注 B」として与えられstd::arrayます。ただし、移動割り当ての複雑さは線形として与えられます。

そのため、ムーブ コンストラクションは基本的にソースからポインターをコピーする以外に選択肢がありません。この場合、イテレーターが無効になる方法を理解するのは困難です。

ただし、移動割り当ての場合、線形の複雑さは、個々の要素を移動元から移動先に移動することを選択できることを意味します。その場合、反復子はほぼ確実に無効になります。

要素の移動割り当ての可能性は、次の説明によって強化されます。「破棄された」部分は、既存のコンテンツを破棄し、ソースからポインターを「盗む」ことに対応しますが、「割り当てられた移動」は、個々の要素をソースから宛先に移動することを示します。

于 2012-06-13T19:36:50.427 に答える
5

tl;dr : はい、 a を移動するstd::vector<T, A>とイテレータが無効になる可能性があります

一般的なケース (std::allocatorインプレース) では、無効化は発生しませんが、保証はなく、実装が現在イテレーターを無効化していないという事実に依存している場合、コンパイラーを切り替えるか、次のコンパイラーの更新でさえ、コードの動作が正しくなくなる可能性があります。


移動中の割り当て:

move-assignment の後にイテレータが実際に有効なままであるかどうかという問題std::vectorは、アロケータのベクトル テンプレートの認識に関連しており、アロケータのタイプ (およびおそらくそのそれぞれのインスタンス) に依存します。

私が見たすべての実装で、std::vector<T, std::allocator<T>>1の移動代入は、実際には反復子またはポインターを無効にしません。ただし、コンテナがアロケータを認識しているため、標準ではイテレータがインスタンスのすべての移動代入に対して有効であることを保証できないため、これを利用することになると問題があります。std::vector

カスタム アロケーターは状態を持っている場合があり、それらが移動代入時に伝搬されず、比較が等しくない場合、ベクターは独自のアロケーターを使用して、移動された要素にストレージを割り当てる必要があります。

させて:

std::vector<T, A> a{/*...*/};
std::vector<T, A> b;
b = std::move(a);

今なら

  1. std::allocator_traits<A>::propagate_on_container_move_assignment::value == false &&
  2. std::allocator_traits<A>::is_always_equal::value == false &&(おそらく c++17 以降)
  3. a.get_allocator() != b.get_allocator()

次に、b新しいストレージを割り当て、要素をa1 つずつそのストレージに移動するため、すべての反復子、ポインター、および参照が無効になります。

その理由は、上記の条件1.を満たすと、コンテナーの移動時にアロケーターの移動割り当てが禁止されるためです。したがって、アロケータの 2 つの異なるインスタンスを処理する必要があります。これらの 2 つのアロケーター オブジェクトが常に等しい ( 2. ) と比較されるわけでも、実際に等しいと比較されるわけでもない場合、両方のアロケーターの状態が異なります。アロケータは、異なる状態xの別のアロケータのメモリの割り当てを解除できない場合があるため、アロケータを持つコンテナは、 を介してメモリを割り当てたコンテナからメモリを盗むことはできません。yxy

アロケータが移動代入で伝播する場合、または両方のアロケータが同等であると比較した場合、ストレージを適切に解放できることが確実であるため、実装はb独自のデータを作成することを選択する可能性が非常に高くなります。a

1 :std::allocator_traits<std::allocator<T>>::propagate_on_container_move_assignment化れていない) のstd::allocator_traits<std::allocator<T>>::is_always_equaltypdef です。std::true_typestd::allocator


移動中の建設:

std::vector<T, A> a{/*...*/};
std::vector<T, A> b(std::move(a));

アロケーター対応コンテナーの移動コンストラクターは、現在の式の移動元のコンテナーのアロケーター インスタンスからそのアロケーター インスタンスを移動構築します。したがって、適切な割り当て解除機能が保証され、メモリが盗まれる可能性があります (実際には盗まれる可能std::array性があります)。

注: イテレータが移動の構築に対しても有効であるという保証はまだありません。


スワップ時:

スワップ後も 2 つのベクトルのイテレータが有効なままであるように要求するのは簡単です (現在は、それぞれのスワップされたコンテナーを指すだけです)。

  1. std::allocator_traits<A>::propagate_on_container_swap::value == true ||
  2. a.get_allocator() == b.get_allocator()

したがって、アロケーターがスワップ時に伝搬されず、それらが等しくない場合、コンテナーのスワップはそもそも未定義の動作です。

于 2016-11-06T03:05:36.353 に答える
4

イテレータが元のコンテナへの参照またはポインタを保持することを妨げるものは何もないため、標準で明示的な保証が見つからない限り、イテレータが有効なままであることに依存することはできません。

于 2012-06-13T19:49:45.157 に答える