web-dev-qa-db-ja.com

STLコンテナの終了反復子に等しい反復子をインクリメントするとどうなりますか

ベクトルの最後の要素を指すときにイテレータを2ずつインクリメントするとどうなりますか? この質問 では、2つの要素によってSTLコンテナーにイテレーターを調整する方法を尋ねる2つの異なるアプローチが提供されます。

  • 算術演算子の形式-+ = 2または++を2回使用する
  • またはstd :: advance()を使用します

イテレータがSTLコンテナの最後の要素またはそれ以降を指している場合、EdgeケースについてVC++ 7でそれらの両方をテストしました。

vector<int> vec;
vec.Push_back( 1 );
vec.Push_back( 2 );

vector<int>::iterator it = vec.begin();
advance( it, 2 );
bool isAtEnd = it == vec.end(); // true
it++; // or advance( it, 1 ); - doesn't matter
isAtEnd = it == vec.end(); //false
it = vec.begin();
advance( it, 3 );
isAtEnd = it == vec.end(); // false

私はベクトルと他のコンテナを横断するときにvector :: end()と比較することを勧める場合があります:

for( vector<int>::iterator it = vec.begin(); it != vec.end(); it++ ) {
    //manipulate the element through the iterator here
}

明らかに、イテレータがループ内の最後の要素を超えて進んだ場合、for-loopステートメントの比較はfalseと評価され、ループは未定義の動作に喜んで継続します。

反復子でadvance()または任意の種類のインクリメント操作を使用し、コンテナの終わりを超えてポイントすると、この状況を検出できないことを正しく理解できますか?もしそうなら、ベストプラクティスは何ですか-そのような進歩を使用しないでください?

60
sharptooth

以下はニコライ・ジョスティスの本からの引用です:

Advance()はシーケンスのend()を超えるかどうかをチェックしないことに注意してください(イテレータは一般に、それが動作するコンテナを知らないためチェックできません)。したがって、この関数を呼び出すと、シーケンスの終わりに対する演算子++の呼び出しが定義されていないため、未定義の動作になる可能性があります。

つまり、イテレーターを範囲内に維持する責任は呼び出し元にあります。

61
Naveen

おそらく次のようなものが必要です:

template <typename Itr>
Itr safe_advance(Itr i, Itr end, size_t delta)
{
    while(i != end && delta--)
        i++;
    return i;
}

iterator_category<Itr>random_access_iterator次のようなことを行います。

return (delta > end - i)? end : i + delta;
13
Motti

イテレータ(it)とvec.begin()のイテレータの間で「距離」関数を使用し、それをベクトルのサイズ(size()で取得)と比較できます。

その場合、forループは次のようになります。

for (vector<int>::iterator it = vec.begin(); distance(vec.begin(), it) < vec.size(); ++it)
{
     // Possibly advance n times here.
}
7
Kostas

Marijnが示唆するコードは、わずかに間違っています(curiousguyが指摘したように)。

最終行の正しいバージョンは次のとおりです。

bool isPastEnd = it >= vec.end();
3
DukeBrymin

Boost.Range をご覧になることをお勧めします。
使用する方が安全かもしれません。
C++ 0xにも含まれます。

2
the_drow

Forステートメントでさらに比較を行うこともできます。

for( vector<int>::iterator it = vec.begin(); it != vec.end() && it+1 != vec.end(); it+=2 ) {
    //manipulate the element through the iterator here
}

これがどのように実行されるかはわかりません Kostas's 提案ですが、それはfeels少しずつ増加する方が良いようです。もちろん、それぞれをチェックする必要があるため、大きな増分ではかなり維持できませんが、別のオプションです。

可能な場合は、絶対に避けます。一度に2つの値をインクリメントする必要がある場合は、std :: pairのベクトルまたは2つの要素を持つ構造体のベクトルを持つことを検討してください。

1
Dolphin

container.end()-終了直後の要素-は唯一定義されている外部値です。

チェックされたイテレータは、本質的に範囲外のアクセスに障害を起こしますが、それはそれほど役に立ちません(特にデフォルトの動作はプログラムを終了することです)。

私はベストプラクティスは「それをしないで」だと思います-イテレータのすべての値をチェックし(フィルタとしてラップされたものが望ましい)、興味深いエントリのみを操作するか、明示的にインデックスを使用します

for(int i = 0; i < vec.size(); i+=2) {...}
1
Steve Gilham