なぜ配列インデックスの代わりにイテレータを使用するのですか?


239

次の2行のコードを見てください。

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

この:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

2番目の方法が好ましいと言われています。これはなぜ正確なのですか?


72
2番目の方法は、に変更some_iterator++することをお勧めし++some_iteratorます。ポストインクリメントは、不要な一時イテレータを作成します。
Jason

6
またend()、宣言条項に持ち込む必要があります。
軌道上での軽量レース、

5
@Tomalak:C ++実装を非効率的に使用vector::endしている人は、ループから外れているかどうかよりも、問題が心配されています。個人的には私は明快さを好みます- findしかし、それが終了条件での呼び出しであった場合、私は心配しました。
Steve Jessop、

13
@Tomalak:そのコードはずさんではありません(まあ、ポストインクリメントかもしれません)。C++イテレータが簡潔さを許可する限り、それは簡潔で明確です。さらに変数を追加すると、時期尚早の最適化のために認知的努力が追加されます。それはだらしないです。
Steve Jessop、

7
@Tomalak:ボトルネックでなければ、時期尚早です。正しい比較がの間ではありませんので、あなたの第二の点は、私には不合理なようだit != vec.end()it != endの間で、それはだ、(vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)(vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it)。文字数を数える必要はありません。どちらかと言えばどちらか一方を優先しますが、他の人があなたの好みに不満を抱くのは「だらし」ではなく、変数が少ない単純なコードの方が好みなので、コードを読んでいるときに考える必要が少なくなります。
Steve Jessop、

回答:


210

最初の形式は、vector.size()が高速な操作である場合にのみ効率的です。これはベクトルには当てはまりますが、たとえばリストには当てはまりません。また、ループの本体内で何を計画していますか?次のように要素にアクセスする予定の場合

T elem = some_vector[i];

次に、コンテナーがoperator[](std::size_t)定義されていることを前提としています。繰り返しますが、これはベクトルには当てはまりますが、他のコンテナには当てはまりません。

イテレータを使用すると、コンテナの独立性に近づくことができます。size()コンテナーにイテレーター機能があることだけを前提として、ランダムアクセス機能や高速操作については想定していません。

標準アルゴリズムを使用して、コードをさらに拡張できます。それはあなたが達成しようとしている何であるかに応じて、使用することを選択するかもしれないstd::for_each()std::transform()というように。明示的なループではなく標準のアルゴリズムを使用することで、ホイールの再発明を回避できます。コードはより効率的で(適切なアルゴリズムが選択されている場合)、正しく、再利用可能です。


8
また、イテレータがフェイルファストになるなどのことを実行できることを忘れていたため、アクセスしている構造に同時に変更が加えられた場合、それについて知っています。整数だけでそれを行うことはできません。
Marcin

4
これは私を混乱させます:「これは、例えばベクトルには当てはまりますが、リストには当てはまりません。」どうして?頭脳を持つ人は誰でもsize_tメンバー変数を追跡し続けますsize()
GManNickG 2009

19
@GMan-ほとんどすべての実装で、size()はリストの場合と同様に高速です。標準の次のバージョンでは、これが真である必要があります。本当の問題は、位置による後退の遅さです。
Daniel Earwicker、2009

8
@GMan:リストサイズを保存するには、リストのスライシングとスプライシングをO(1)ではなくO(n)にする必要があります。

5
C ++ 0xでは、size()メンバー関数は、それをサポートするすべてのコンテナー(を含む)に対して一定の時間の複雑さを持つ必要がありstd::listます。
James McNellis、2010年

54

これは、最新のC ++教化プロセスの一部です。イテレータはほとんどのコンテナを反復する唯一の方法であるため、適切な考え方を身につけるために、ベクトルでも使用できます。真剣に、それが私がそれをする唯一の理由です-私は今までベクターを別の種類のコンテナーに置き換えたことはないと思います。


うわー、これはまだ3週間後の反対投票です。ほんの少しの口先だけでは意味がないと思います。

配列のインデックスの方が読みやすいと思います。これは、他の言語で使用されている構文、および旧式のC配列で使用されている構文と一致しています。また、それほど冗長ではありません。コンパイラーが優れていて、とにかくそれが問題になるケースがほとんどない場合、効率は洗い流されるべきです。

それでも、ベクトルでイテレータを頻繁に使用していることに気づきました。イテレータは重要な概念だと思うので、できる限りそれを促進します。


1
C ++イテレータも概念的にひどく壊れています。ベクトルの場合、エンドポインターが実際にはend + 1(!)であるため、捕まってしまいました。ストリームの場合、イテレーターモデルは非現実的です-存在しない架空のトークンです。リンクされたリストについても同様です。このパラダイムは、配列に対してのみ意味があり、それほど意味がありません。1つだけでなく2つのイテレータオブジェクトが必要なのはなぜですか
Tuntable

5
@aberglas彼らはまったく壊れていません、あなたはそれらに慣れていないだけです。ハーフオープン範囲は一般的な概念であり、直接アクセスすることを意図していないセンチネルはプログラミング自体と同じくらい古いものです。
Mark Ransom

4
ストリームイテレータを見て、パターンに合わせるために==が何をするために変態されているかを考え、イテレータが壊れていないことを教えてください!またはリンクされたリストの場合。配列の場合でも、末尾を1つ超えて指定する必要があるというのは、壊れたCスタイルのアイデアです。それらは、JavaまたはC#または他の言語のイテレータのようであり、(2つのオブジェクトではなく)1つのイテレータが必要で、単純な終了テストが必要です。
2016

53

some_vectorリストの特定の実装にコードを結び付けていないからです。配列インデックスを使用する場合は、何らかの形式の配列である必要があります。イテレータを使用する場合、そのコードを任意のリスト実装で使用できます。


23
std :: listインターフェースは、O(n)になるため、意図的にoperator [](size_t n)を提供しません。
MSalters 2008

33

some_vectorがリンクリストで実装されていると想像してください。次に、i番目のアイテムを要求するには、ノードのリストをトラバースするためにi操作を実行する必要があります。イテレータを使用する場合、一般的に言えば、できるだけ効率的になるように最善を尽くします(リンクリストの場合、現在のノードへのポインタを維持し、各反復でそれを進めます。単一操作)。

したがって、次の2つが提供されます。

  • 使用の抽象化:一部の要素を反復したいだけで、それを行う方法は気にしません
  • パフォーマンス

1
「現在のノードへのポインターを維持し、それを前進させます[効率についての良い点]」-そうです、なぜ人々がイテレータの概念を理解するのに苦労しているのかわかりません。それらは概念的には単なるポインタのスーパーセットです。要素へのポインタをキャッシュするだけでよいのに、ある要素のオフセットを何度も何度も計算するのはなぜですか?まあ、それはイテレータもやっていることです。
underscore_d

27

私はここで悪魔の擁護者になり、反復子を推奨しません。その主な理由は、デスクトップアプリケーションの開発からゲーム開発までに取り組んだすべてのソースコードに、イテレータを使用する必要がなかったからです。常にそれらが必要とされるわけではなく、次にイテレータで得られる隠された仮定とコードの混乱とデバッグの悪夢は、速度を必要とするアプリケーションでそれを使用しない主要な例です。

メンテナンスの観点からも、それらは混乱しています。それらのためではなく、舞台裏で発生するすべてのエイリアシングのためです。標準とは完全に異なる何かを行う独自の仮想ベクトルまたは配列リストを実装していないことを知る方法 ランタイム中に現在、どのタイプなのか知っていますか?すべてのソースコードをチェックする時間がなかった演算子をオーバーロードしましたか?地獄私はあなたが使っているSTLのバージョンを知っていますか?

イテレータで次に発生する問題は、リークの多い抽象化ですが、これについて詳細に説明しているWebサイトは多数あります。

申し訳ありませんが、イテレータのポイントはまだ見ていません。彼らがあなたからリストまたはベクトルを抽象化している場合、実際にはあなたはすでにどのベクターを知っているべきか、そうでなければあなたが扱っているリストを知っているはずです。


23

反復処理中にベクターにアイテムを追加/削除する場合は、イテレーターを使用することをお勧めします。

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

インデックスを使用している場合は、挿入と削除を処理するために、配列内でアイテムを上下にシャッフルする必要があります。


3
コンテナーの中央に要素を挿入する場合は、ベクターはコンテナーを選択するのに適していません。もちろん、イテレータが優れている理由に戻ります。リストに切り替えるのは簡単です。
ヴィルヘルムテル2008

ただし、の代わりにリンクされたリストを使用することをお勧めしている場合、すべての要素を反復処理することは、にstd::list比べてかなりコストがかかりstd::vectorますstd::vector。43ページを参照してください:ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf私の経験では、すべてを検索して任意の位置にある要素を削除する場合std::vectorよりも、が速いことがわかりましたstd::list
デビッドストーン

インデックスは安定しているため、挿入と削除に必要な追加のシャッフルはわかりません。
musiphil 2013年

...そしてここで使用する必要があるリンクリストを使用すると、ループステートメントはfor (node = list->head; node != NULL; node = node->next)最初の2行のコード(宣言とループヘッド)をまとめたものよりも短くなります。つまり、繰り返しforますが、イテレータを使用することと使用しないことの間の簡潔さには基本的な違いはほとんどありません- while宣言、反復、終了の確認を使用しても、ステートメントの3つの部分は満たされます。
エンジニア

16

関心事の分離

ループの「コア」の懸念から反復コードを分離するのはとても良いことです。それはほとんど設計上の決定です。

実際、インデックスによる反復は、コンテナーの実装に結び付けられます。コンテナに開始と終了のイテレータを要求すると、ループコードが他のコンテナタイプで使用できるようになります。

また、std::for_each方法では、尋ねるのではなく、コレクションに何をすべきかを伝えます内部について何かで

0x標準はクロージャを導入する予定です。これにより、このアプローチがはるかに使いやすくなります。たとえば、Rubyの表現力を見てください [1..6].each { |i| print i; }。 ...

パフォーマンス

しかし、おそらく非常に見過ごされがちな問題は、このfor_eachアプローチを使用すると、反復を並列化する機会が得られることです。インテルスレッディングブロックは、システム内のプロセッサの数全体にコードブロックを分散できます。

注:algorithmsライブラリ、特にを発見した後foreach、私は2〜3か月間、途方もなく小さい「ヘルパー」演算子構造体を作成しました。これにより、他の開発者が夢中になります。この後、私は実用的なアプローチに戻りました-小さなループ本体はもはや価値がありませforeachん:)

イテレータに関する必読のリファレンスは、「Extended STL」という本です。

GoFには、イテレーターパターンの最後に小さな段落があり、このブランドの反復について説明しています。これは「内部イテレータ」と呼ばれます。見ているこちらも。


15

よりオブジェクト指向だからです。あなたが仮定しているインデックスで反復している場合:

a)それらのオブジェクトが順序付けされている
b)それらのオブジェクトがインデックスによって取得できる
c)インデックスの増分がすべてのアイテムにヒットする
d)そのインデックスがゼロから始まる

イテレータを使用すると、基礎となる実装が何であるかを知らなくても、「すべてを提供して、それで作業できるようにする」と言っていることになります。(Javaでは、インデックスを介してアクセスできないコレクションがあります)

また、イテレータを使用すると、配列の境界を超えることを心配する必要がありません。


2
「オブジェクト指向」は正しい用語だとは思いません。イテレータは、設計において「オブジェクト指向」ではありません。これらは、アルゴリズムをクラスから分離することを奨励するため、オブジェクト指向プログラミングよりも関数型プログラミングを促進します。
ヴィルヘルムテル2008

また、イテレータは範囲外にならないようにするのに役立ちません。標準のアルゴリズムではできますが、イテレータだけではできません。
ヴィルヘルムテル2008

@wilhelmtellで十分ですが、私は明らかにJava中心の観点からこれを考えています。
シニカルマン2008

1
また、コレクションの操作とコレクションの実装を分離しているため、オブジェクト指向を促進すると思います。オブジェクトのコレクションは、それらを扱うためにどのアルゴリズムを使用する必要があるかを必ずしも知っている必要はありません。
シニカルマン2008

実際には、イテレータをチェックしたSTLのバージョンがあります。つまり、そのイテレータで何かを行おうとすると、ある種の範囲外の例外がスローされます。
Daemin

15

イテレータのもう1つの優れた点は、const-preferenceをより適切に表現(および適用)できることです。この例では、ループの最中にベクターを変更しないようにします。


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}

これは妥当に見えますが、それがの理由であるかどうかはまだ疑問ですconst_iterator。ループ内でベクターを変更する場合、理由のために変更します。変更の99.9%は偶然ではなく、残りの部分は、コード内のあらゆる種類のバグのような単なるバグです。修正する必要があります。Javaや他の多くの言語では、constオブジェクトはまったくありませんが、それらの言語のユーザーは、それらの言語でのconstサポートがないという問題を抱えることはありません。
neevek

2
@neevekそれがの理由ではない場合、その理由const_iteratorはいったい何でしょうか?
underscore_d 2017年

@underscore_d、私も不思議に思っています。私はこれについての専門家ではありません。答えが私に納得できないというだけです。
neevek

15

他のすべての優れた答えは別として... intベクトルに対して十分な大きさではない場合があります。代わりに、インデックスを使用するsize_type場合は、コンテナにを使用します。

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}

1
@Pat Notz、それは非常に良い点です。STLベースのWindowsアプリケーションをx64に移植する過程で、size_tをintに割り当てると、切り捨てが発生する可能性があるという何百もの警告に対処する必要がありました。
bk1e 2008

1
サイズタイプがunsignedであり、intがsignedであることは言うまでもありません。そのため、と比較int iするだけで、直感的ではなく、バグを隠す変換が行われmyvector.size()ます。
エイドリアンマッカーシー

12

私はおそらくあなたが電話することもできることを指摘しなければなりません

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);


7

STLイテレータは主にそこにあるので、sortなどのSTLアルゴリズムはコンテナに依存しません。

ベクトルのすべてのエントリをループするだけの場合は、インデックスループスタイルを使用します。

ほとんどの人間にとって、タイピングが少なく、解析が簡単です。C ++に、テンプレートマジックを使い果たすことなく、単純なforeachループがあればよいでしょう。

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'

5

私はそれがベクトルに大きな違いをもたらすとは思いません。私はインデックスをより読みやすくするために自分で使用することを好みます。必要に応じて、6つのアイテムを前方にジャンプしたり後方にジャンプしたりするなど、ランダムアクセスを実行できます。

また、次のようにループ内のアイテムを参照するので、場所の周りに角括弧が多くありません。

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

将来的にベクターをリストに置き換える必要があり、STLフリークにはスタイリッシュに見える可能性がある場合は、イテレーターを使用することをお勧めしますが、他の理由は考えられません。


ほとんどのアルゴリズムは、コンテナの各要素に対して1回ずつ順番に動作します。もちろん、特定の順序または方法でコレクションをトラバースしたいという例外はありますが、この場合は、STLと統合してイテレータと連携するアルゴリズムを一生懸命書いてみます。
wilhelmtell 2008

これにより、再利用が促進され、後でオフバイワンエラーが回避されます。その後、イテレータを使用して、他の標準アルゴリズムと同じようにそのアルゴリズムを呼び出します。
wilhelmtell 2008

1
advance()も必要ありません。イテレーターには、インデックスと同じ+ =および-=演算子があります(ベクターおよびベクターのようなコンテナーの場合)。
MSalters 2008

I prefer to use an index myself as I consider it to be more readable一部の状況でのみ。他のものでは、インデックスはすぐに非常に乱雑になります。and you can do random accessこれはインデックスのユニークな機能ではありません。en.cppreference.com/ w / cpp / concept / RandomAccessIteratorを
underscore_d

3

2番目の形式は、より正確に何をしているのかを表します。あなたの例では、あなたは本当にiの値を気にしません-欲しいのはイテレータの次の要素だけです。


3

この回答のテーマについてもう少し学んだ後、私はそれが少し単純化しすぎていることに気付きました。このループの違い:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

そしてこのループ:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

かなり最小限です。実際、この方法でループを実行する構文は、私には次のように成長しているようです。

while (it != end){
    //do stuff
    ++it;
}

イテレータはかなり強力な宣言型機能のいくつかをアンロックします。STLアルゴリズムライブラリと組み合わせると、配列インデックスの管理の範囲外であるかなりクールなことができます。


真実は、すべてのイテレータがあなたの最後の例と同じくらいコンパクトだった場合、箱から出してすぐなら、私はそれらについてほとんど問題がないでしょう。もちろん、これは実際にはfor (Iter it = {0}; it != end; ++it) {...}-宣言を省略したのと同じなので、簡潔さは2番目の例とそれほど変わりません。それでも、+ 1。
エンジニア

3

インデックス作成には追加のmul操作が必要です。たとえば、のvector<int> v場合、コンパイラはに変換v[i]され&v + sizeof(int) * iます。


ほとんどの場合、イテレータに比べてそれほど大きなデメリットはないかもしれませんが、注意する必要があります。
nobar 2010年

3
孤立した単一要素のアクセスの場合は、おそらく。しかし、ループについて話している場合-OPのように-この答えは架空の非最適化コンパイラに基づいていると確信しています。半ば適切なものはすべてsizeof、オフセット全体を毎回もう一度計算するのではなく、キャッシュする機会と可能性が十分あり、反復ごとに1回追加するだけです。
underscore_d

2

イテレーション中、処理するアイテムの数を知る必要はありません。必要なのはアイテムだけで、イテレータはそのようなことを非常にうまく行います。


2

インデックスの利点の1つは、次のような連続したコンテナに追加しても無効にならないことです。 std::vectorことです。そのため、反復中にアイテムをコンテナに追加できます。

これはイテレータでも可能ですが、を呼び出すreserve()必要があるため、追加するアイテムの数を知る必要があります。


1

すでにいくつかの良い点があります。追加のコメントがいくつかあります。

  1. 私たちがC ++標準ライブラリについて話していると仮定すると、「ベクター」は、C配列(ランダムアクセス、連続メモリレイアウトなど)が保証されているランダムアクセスコンテナーを意味します。「some_container」と言った場合、上記の回答の多くはより正確になります(コンテナーの独立性など)。

  2. コンパイラの最適化への依存を排除​​するには、次のように、インデックス付きコードのループからsome_vector.size()を移動します。

    const size_t numElems = some_vector.size();
    for(size_t i = 0; i 
  3. 常に事前に増分する反復子と、事後の増分を例外的なケースとして扱います。

for(some_iterator = some_vector.begin(); some_iterator!= some_vector.end(); ++ some_iterator){//何かを行う}

したがってstd::vector<>、コンテナーのようにインデックス付け可能であると仮定すると、コンテナーを順にたどりながら、どちらか一方を優先する理由はありません。古い要素または新しい要素のインデックスを頻繁に参照する必要がある場合は、インデックス付きバージョンの方が適切です。

一般に、アルゴリズムはイテレーターを使用し、イテレーターのタイプを変更することで動作を制御(および暗黙的に文書化)できるため、イテレーターの使用が推奨されます。配列の場所はイテレータの代わりに使用できますが、構文上の違いが際立ちます。


1

foreachステートメントを嫌うのと同じ理由で、イテレータを使用しません。複数の内部ループがある場合、ローカル値とイテレーター名をすべて覚えておく必要なしに、グローバル/メンバー変数を追跡するのは十分に困難です。私が便利だと思うのは、さまざまな場面で2セットのインデックスを使用することです。

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

たとえば、「animation_matrices [i]」のようなものをランダムな「anim_matrix」という名前のイテレータに省略したくありません。これは、この値がどの配列から由来しているかがはっきりとわからないためです。


この意味で、インデックスがどのように優れているかはわかりません。あなたは簡単にイテレータを使用し、その名前だけのための規則を選ぶことができます:itjtkt、などあるいは単に使用を継続ijkなど、そして、あなたは次のように私には、その後、反復子が表す内容を正確に把握するために何かが必要な場合はfor (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()、より説明的になりますのように継続的にインデックスを付ける必要があるよりもanimation_matrices[animIndex][boneIndex]
underscore_d 2017年

うわー、私がその意見を書いたのは何年も前のようです。最近では、多くのことをせずにforeachとc ++イテレータの両方を使用しています。バグのあるコードを何年も使って作業すると許容範囲が
増える

ははは、確かに、私はこれが何歳になるかを実際には見ていませんでした!前回どうしても思いつかなかった他のことは、最近では範囲ベースのforループもあり、これによりイテレーターベースの方法がさらに簡潔になったことです。
underscore_d

1
  • 金属に近いことが好きな場合、または実装の詳細を信頼しない場合は、イテレータを使用しないでください。
  • 開発中にあるコレクションタイプを別のコレクションタイプに定期的に切り替える場合は、イテレータを使用します。
  • さまざまな種類のコレクションを反復する方法を覚えるのが難しい場合(おそらく、使用中のいくつかの異なる外部ソースからの複数のタイプがある場合)、イテレータを使用して、要素の上を歩く方法を統一します。これは、リンクリストと配列リストを切り替える場合にも当てはまります。

本当に、これですべてです。どちらかといえば平均してどちらかというと簡潔さを増すつもりはありません。簡潔さが本当に目標であれば、いつでもマクロに頼ることができます。


1

C ++ 11機能にアクセスできる場合は、範囲ベースのforループを使用して、次のようにベクター(または他のコンテナー)を反復することもできます。

for (auto &item : some_vector)
{
     //do stuff
}

このループの利点はitem、インデックスをめちゃくちゃにしたり、イテレータを逆参照するときに間違いを犯したりするリスクを冒すことなく、変数を介してベクトルの要素に直接アクセスできることです。さらに、プレースホルダーautoを使用すると、コンテナー要素のタイプを繰り返す必要がなくなり、コンテナーに依存しないソリューションにさらに近づくことができます。

ノート:

  • ループ内に要素インデックスが必要であり、operator[]コンテナに存在する(そして十分に高速である)場合は、最初の方法を使用することをお勧めします。
  • 範囲ベースのforループは、コンテナーへの要素の追加/コンテナーからの要素の削除には使用できません。あなたがそれをしたいなら、ブライアン・マシューズによって与えられた解決策に固執してください。
  • コンテナの要素を変更したくない場合はconst、次のようにキーワードを使用する必要がありますfor (auto const &item : some_vector) { ... }

0

「何をすべきかをCPUに伝える」(命令型)よりも優れているのは、「ライブラリに必要なことを伝える」(機能的)ということです。

したがって、ループを使用する代わりに、stlに存在するアルゴリズムを学ぶ必要があります。



0

私の多くのアプリケーションは「サムネイル画像の表示」のようなものを必要とするため、私は常に配列インデックスを使用します。だから私はこのようなものを書きました:

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}

0

どちらの実装も正しいですが、私は「for」ループを好むでしょう。他のコンテナではなくベクターを使用することを決定したので、インデックスを使用するのが最良のオプションです。Vectorでイテレータを使用すると、オブジェクトを連続メモリブロックに配置して、アクセスを容易にするという非常に大きなメリットが失われます。


2
「ベクターでイテレーターを使用すると、オブジェクトを連続メモリブロックに配置して、アクセスを容易にするという非常に大きなメリットが失われます。」[引用が必要]。どうして?隣接するコンテナーへのイテレーターの増分は、単純な追加として実装できないと思いますか?
underscore_d

0

ここでの答えのどれも、コンテナへのインデックス付けに関する一般的な概念としてイテレータが好きな理由を説明していないと感じました。イテレーターを使用した私の経験のほとんどは、実際にはC ++からではなく、Pythonなどの高水準プログラミング言語からのものであることに注意してください。

イテレーターインターフェースは、関数のコンシューマーに課す要件が少ないため、コンシューマーはそれを使ってより多くのことができます。

必要なのは、フォワードイテレートが可能である場合、開発者はインデックス可能なコンテナの使用に限定されずoperator++(T&)operator*(T)とを実装する任意のクラスを使用できますoperator!=(const &T, const &T)

#include <iostream>
template <class InputIterator>
void printAll(InputIterator& begin, InputIterator& end)
{
    for (auto current = begin; current != end; ++current) {
        std::cout << *current << "\n";
    }
}

// elsewhere...

printAll(myVector.begin(), myVector.end());

アルゴリズムは、必要な場合(ベクトルの反復処理)で機能しますが、必ずしも予期していないアプリケーションにも役立ちます。

#include <random>

class RandomIterator
{
private:
    std::mt19937 random;
    std::uint_fast32_t current;
    std::uint_fast32_t floor;
    std::uint_fast32_t ceil;

public:
    RandomIterator(
        std::uint_fast32_t floor = 0,
        std::uint_fast32_t ceil = UINT_FAST32_MAX,
        std::uint_fast32_t seed = std::mt19937::default_seed
    ) :
        floor(floor),
        ceil(ceil)
    {
        random.seed(seed);
        ++(*this);
    }

    RandomIterator& operator++()
    {
        current = floor + (random() % (ceil - floor));
    }

    std::uint_fast32_t operator*() const
    {
        return current;
    }

    bool operator!=(const RandomIterator &that) const
    {
        return current != that.current;
    }
};

int main()
{
    // roll a 1d6 until we get a 6 and print the results
    RandomIterator firstRandom(1, 7, std::random_device()());
    RandomIterator secondRandom(6, 7);
    printAll(firstRandom, secondRandom);

    return 0;
}

イテレータの実装は比較的簡単ですが、このイテレータに似た機能を実行する角かっこ演算子を実装しようとすると、不自然になります。角かっこ演算子は、クラスの機能(任意のポイントにインデックスを付けることができる)にも影響を与えます。これは、実装が困難または非効率的な場合があります。

イテレータは装飾にも適してます。人々は、コンストラクターでイテレーターを取り、その機能を拡張するイテレーターを作成できます。

template<class InputIterator, typename T>
class FilterIterator
{
private:
    InputIterator internalIterator;

public:
    FilterIterator(const InputIterator &iterator):
        internalIterator(iterator)
    {
    }

    virtual bool condition(T) = 0;

    FilterIterator<InputIterator, T>& operator++()
    {
        do {
            ++(internalIterator);
        } while (!condition(*internalIterator));

        return *this;
    }

    T operator*()
    {
        // Needed for the first result
        if (!condition(*internalIterator))
            ++(*this);
        return *internalIterator;
    }

    virtual bool operator!=(const FilterIterator& that) const
    {
        return internalIterator != that.internalIterator;
    }
};

template <class InputIterator>
class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
{
public:
    EvenIterator(const InputIterator &internalIterator) :
        FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
    {
    }

    bool condition(std::uint_fast32_t n)
    {
        return !(n % 2);
    }
};


int main()
{
    // Rolls a d20 until a 20 is rolled and discards odd rolls
    EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
    EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
    printAll(firstRandom, secondRandom);

    return 0;
}

これらのおもちゃはありふれたものに見えるかもしれませんが、イテレータやイテレータデコレータを使用してシンプルなインターフェースで強力なことを実行することを想像するのは難しくありません。 。これらのパターンにより、無限セットのメモリー効率の良い反復が可能になり、上で書いたようなフィルターを使用して、結果の遅延評価が行われる可能性があります。

C ++テンプレートの強力な機能の1つは、固定長のC配列などに適用すると、単純で効率的なポインター演算減衰するイテレーターインターフェースであり、これを真にゼロコストの抽象化にします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.