web-dev-qa-db-ja.com

配列インデックスの代わりにイテレータを使用する理由

次の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番目の方法が好ましいと言われています。なぜこれが正確ですか?

223
Jason Baker

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

T elem = some_vector[i];

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

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

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

198
wilhelmtell

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

51
cruizer

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


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

それでも、私はまだベクターでイテレータを頻繁に使用していることに気付きます。イテレータは重要な概念であると考えているので、できる限りプロモーションします。

48
Mark Ransom

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

そのため、次の2つのことが提供されます。

  • 使用の抽象化:いくつかの要素を反復したいだけで、その方法は気にしません
  • 性能
33
asterite

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

メンテナンスの観点から見ても、彼らは混乱しています。それらのためではなく、舞台裏で発生するすべてのエイリアシングのためです。標準とはまったく異なることを行う独自の仮想ベクトルまたは配列リストを実装していないことをどのようにして知ることができますか?現在、実行中に現在どのタイプかを知っていますか?あなたはすべてのソースコードをチェックする時間がありませんでした。使用しているSTLのバージョンさえ知っていますか?

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

申し訳ありませんが、私はイテレータのポイントを見たことがないし、まだ見ていません。それらがリストまたはベクトルをあなたから遠ざけている場合、実際にあなたがすでにどのベクトルまたはリストを扱っているかを知っている必要があります。

26
Chad

繰り返し処理中にベクターにアイテムを追加/削除する場合は、イテレーターを使用できます。

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

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

23
Brian Matthews

関心事の分離

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

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

また、std::for_eachのように、 コレクションに何をすべきかを伝え、代わりにその内部について何かを尋ねる

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

性能

しかし、おそらく大いに見落とされている問題は、for_eachアプローチを使用すると、反復を並列化する機会が得られることです- intel threading blocks システムで!

注:algorithmsライブラリー、特にforeachライブラリーを発見した後、2、3か月間、途方もなく小さな「ヘルパー」オペレーター構造を作成しました。この後、実用的なアプローチに戻りました。小さなループボディはforeachに値しません:)

イテレータに関する必読の参照は、本 "Extended STL" です。

GoFのIteratorパターンの最後に小さなパラグラフがあり、このブランドの反復について説明しています。 「内部イテレータ」と呼ばれます。こちらもご覧ください。

16
xtofl

よりオブジェクト指向だからです。インデックスを反復処理する場合、次のことを想定しています:

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

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

また、イテレータを使用すると、配列の範囲外になることを心配する必要がありません。

14
cynicalman

他のすべての優れた答えは別として... 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
}
14
Pat Notz

イテレータのもう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
}
14
Pat Notz

私はおそらくあなたが電話することもできることを指摘する必要があります

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

11
MSalters

ほとんどの場合、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
}
'
6
Jeroen Dirks

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

また、このようなループ内の項目への参照を作成するので、場所の周りに角括弧があまりありません。

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

    // Do stuff to "item".
}

イテレータの使用は、将来のある時点でベクトルをリストに置き換える必要があると思われる場合に役立ちます。また、STLフリークにとってはよりスタイリッシュに見えますが、他の理由は考えられません。

5
Adam Pierce

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

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アルゴリズムライブラリと組み合わせると、配列インデックス管理の範囲外の非常に優れた機能を実行できます。

3
Jason Baker

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

3
Marc Eaddy

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

3
Colen

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

2

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

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

2
danpla

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

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

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

    const size_t numElems = some_vector.size(); 
     for(size_t i = 0; i 
  3. 常にプリインクリメントのイテレータを使用し、ポストインクリメントを例外的なケースとして扱います。

そのため、std::vector<>のようなコンテナをインデックス付け可能と想定し、コンテナを順番に通過して他のコンテナより優先する理由はありません。古いまたは新しいエレメントインデックスを頻繁に参照する必要がある場合は、インデックスバージョンの方が適切です。

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

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

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

1
Engineer

Foreach-statementsを嫌うのと同じ理由で、イテレータを使用しません。複数の内部ループがある場合、すべてのローカル値とイテレータ名も覚えていなくても、グローバル/メンバー変数を追跡するのは十分に困難です。私が便利だと思うのは、さまざまな場面で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」という名前のイテレータに短縮したくはありません。なぜなら、この配列がどの配列から生成されたのか明確にわからないからです。

1
AareP

コンテナ独立のため

0
all2one

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

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
Krirk

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

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

0
Cyber Oliveira

どちらの実装も正しいですが、「for」ループの方が好きです。他のコンテナではなくベクターを使用することにしたので、インデックスを使用するのが最適なオプションです。 Vectorでイテレータを使用すると、オブジェクトが連続メモリブロックにあるという利点が失われ、アクセスが容易になります。

0
Messiah