次の 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番目の方法が好ましいと言われています。これはなぜですか?
次の 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番目の方法が好ましいと言われています。これはなぜですか?
最初の形式は、 vector.size() が高速な操作である場合にのみ効率的です。これはベクトルには当てはまりますが、たとえばリストには当てはまりません。また、ループの本体内で何をする予定ですか? 次のように要素にアクセスする予定がある場合
T elem = some_vector[i];
operator[](std::size_t)
次に、コンテナが定義したと仮定しています。繰り返しますが、これはベクターには当てはまりますが、他のコンテナーには当てはまりません。
イテレータを使用すると、コンテナの独立性に近づきます。ランダムアクセス機能や高速size()
操作については仮定していませんが、コンテナーにイテレーター機能があることだけを想定しています。
標準アルゴリズムを使用して、コードをさらに拡張できます。達成しようとしていることに応じてstd::for_each()
、std::transform()
などを使用することを選択できます。明示的なループではなく標準のアルゴリズムを使用することで、車輪の再発明を避けることができます。あなたのコードはより効率的で (適切なアルゴリズムが選択されている場合)、正しく、再利用可能です。
これは、最新の C++ 教化プロセスの一部です。イテレーターは、ほとんどのコンテナーを反復処理する唯一の方法であるため、適切な考え方を身に付けるためだけにベクターでも使用します。真剣に、それが私がそうする唯一の理由です。ベクターを別の種類のコンテナに置き換えたことはないと思います。
配列インデックスの方が読みやすいと思います。これは、他の言語で使用される構文や、旧式の C 配列に使用される構文と一致します。また、冗長性も低くなります。コンパイラが優れている場合、効率は問題になるはずであり、とにかくそれが問題になるケースはほとんどありません。
それでも、ベクターでイテレータを頻繁に使用していることに今でも気付きます。イテレータは重要な概念であると信じているので、できる限り推進しています。
コードを some_vector リストの特定の実装に結び付けていないためです。配列インデックスを使用する場合は、何らかの形式の配列でなければなりません。イテレータを使用すると、そのコードを任意のリスト実装で使用できます。
some_vector がリンクリストで実装されていると想像してください。次に、i 番目の場所にある項目を要求するには、ノードのリストをトラバースするために i 回の操作を行う必要があります。ここで、イテレータを使用する場合、一般的に言えば、可能な限り効率的になるように最善を尽くします (リンクされたリストの場合、現在のノードへのポインタを維持し、各反復でそれを進めます。単一操作)。
したがって、次の 2 つのことが提供されます。
私はここで悪魔の擁護者になり、イテレータはお勧めしません。主な理由は、デスクトップアプリケーションの開発からゲームの開発まで、私が取り組んできたすべてのソースコードに、イテレータを使用する必要がないことです。常にそれらは必要とされておらず、第二に、イテレータで発生する隠れた仮定とコードの混乱、およびデバッグの悪夢は、速度を必要とするアプリケーションでそれを使用しないための代表的な例です。
メンテナンスの観点からさえ、彼らは混乱しています。それはそれらのせいではなく、舞台裏で起こるすべてのエイリアシングのせいです。標準とはまったく異なることを行う独自の仮想ベクトルまたは配列リストを実装していないことをどのように知ることができますか。実行時に現在どのタイプが使用されているか知っていますか?オペレーターをオーバーロードしましたか?すべてのソースコードをチェックする時間がありませんでした。地獄私はあなたが使用しているSTLのバージョンさえ知っていますか?
イテレータで発生する次の問題は、リークのある抽象化ですが、これについて詳細に説明しているWebサイトは多数あります。
申し訳ありませんが、イテレータのポイントはまだ確認されていません。彼らがあなたから離れてリストまたはベクトルを抽象化する場合、実際にはあなたがどのベクトルまたはあなたが扱っているリストをすでに知っているべきであるかを知らなければ、あなたは将来いくつかの素晴らしいデバッグセッションのためにあなた自身をセットアップするつもりです。
ベクトルを繰り返し処理しているときに項目をベクトルに追加/削除する場合は、反復子を使用することをお勧めします。
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;
}
}
インデックスを使用している場合は、挿入と削除を処理するために、配列内でアイテムを上下にシャッフルする必要があります。
反復コードをループの「コア」の問題から分離することは非常に良いことです。それはほとんど設計上の決定です。
実際、インデックスによる反復は、コンテナーの実装に結び付けられます。コンテナに開始イテレータと終了イテレータを要求すると、ループ コードを他のコンテナ タイプで使用できるようになります。
また、std::for_each
途中で、内部について何かを尋ねるのではなく、何をすべきかをコレクションに伝えます。
0x 標準はクロージャを導入する予定で、これによりこのアプローチがより使いやすくなります - たとえば Ruby の表現力を見てください[1..6].each { |i| print i; }
...
しかし、おそらく見過ごされがちな問題は、このfor_each
アプローチを使用すると、反復を並列化する機会が得られることです。インテルのスレッド化ブロックは、システム内のプロセッサの数にコード ブロックを分散できます!
注:algorithms
ライブラリ、特にを発見した後foreach
、仲間の開発者を夢中にさせる途方もなく小さな「ヘルパー」演算子構造体を 2 ~ 3 か月書きました。この後、私は実用的なアプローチに戻りました-小さなループ本体はもう価値がありませforeach
ん:)
イテレータに関する必読の参考書は、「Extended STL」という本です。
GoF には Iterator パターンの最後に小さな段落があり、この一連の反復について説明しています。これは「内部イテレータ」と呼ばれます。こちらもご覧ください。
よりオブジェクト指向だからです。想定しているインデックスを使用して反復している場合:
a) それらのオブジェクトが順序付けられている
こと b) それらのオブジェクトがインデックスによって取得できること
c) インデックスのインクリメントがすべてのアイテムにヒットすること
d) そのインデックスがゼロから始まること
イテレータを使用すると、基礎となる実装が何であるかを知らなくても、「それで作業できるようにすべてを渡してください」と言っています。(Java では、インデックスを介してアクセスできないコレクションがあります)
また、反復子を使用すると、配列の範囲外になることを心配する必要はありません。
他のすべての優れた回答は別として...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 つの優れた点は、const 設定をより適切に表現 (および強制) できることです。この例では、ループの途中でベクトルを変更しないようにします。
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
}
電話をかけることもできます。
std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);
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 アイテムを前方にジャンプしたり、後方にジャンプしたりするなどのランダム アクセスを実行できるからです。
また、次のようにループ内のアイテムへの参照を作成することも好きなので、場所の周りに多くの角括弧がありません:
for(size_t i = 0; i < myvector.size(); i++)
{
MyClass &item = myvector[i];
// Do stuff to "item".
}
将来のある時点でベクトルをリストに置き換える必要があると思われる場合は、イテレータを使用するのが良い場合があります。また、STL フリークにはよりスタイリッシュに見えますが、他の理由は考えられません。
インデックス作成には追加のmul
操作が必要です。たとえば、 の場合vector<int> v
、コンパイラは に変換v[i]
し&v + sizeof(int) * i
ます。
この回答の主題についてもう少し学んだ後、私はそれが少し単純化しすぎていることに気付きました. このループの違い:
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 アルゴリズム ライブラリと組み合わせると、配列インデックス管理の範囲外でかなりクールなことを行うことができます。
2 番目の形式は、何をしているのかをより正確に表しています。あなたの例では、 i の値は気にしません。必要なのは、イテレータの次の要素だけです。
反復中に、処理するアイテムの数を知る必要はありません。アイテムが必要なだけで、イテレータはそのようなことを非常にうまく行います。
C++11機能にアクセスできる場合は、次のように範囲ベースのfor
ループを使用してベクター (またはその他のコンテナー) を反復処理することもできます。
for (auto &item : some_vector)
{
//do stuff
}
このループの利点は、item
変数を介してベクトルの要素に直接アクセスできることです。インデックスを台無しにしたり、反復子を逆参照するときに間違いを犯したりするリスクはありません。さらに、プレースホルダーauto
を使用すると、コンテナー要素の型を繰り返す必要がなくなり、コンテナーに依存しないソリューションにさらに近づきます。
ノート:
operator[]
コンテナーに存在する (そして十分に高速である) 場合は、最初の方法を使用することをお勧めします。for
ループを使用して、コンテナーへの要素の追加/コンテナーからの要素の削除を行うことはできません。それを行いたい場合は、Brian Matthewsが提供する解決策に固執することをお勧めします。const
を次のように使用する必要がありますfor (auto const &item : some_vector) { ... }
。すでにいくつかの良い点。追加のコメントがいくつかあります。
C++ 標準ライブラリについて話していると仮定すると、「ベクトル」は、C 配列の保証 (ランダム アクセス、連続したメモリ レイアウトなど) を持つランダム アクセス コンテナーを意味します。「some_container」と言った場合、上記の回答の多くはより正確になります (コンテナーの独立性など)。
コンパイラの最適化への依存を排除するには、次のように、インデックス付きコードのループから some_vector.size() を移動できます。
const size_t numElems = some_vector.size(); for (size_t i = 0; i
常にイテレータをプリインクリメントし、ポストインクリメントを例外的なケースとして扱います。
したがって、コンテナのようにインデックス可能であると仮定するとstd::vector<>
、コンテナを順番に通過して、一方を優先する正当な理由はありません。古いまたは新しい要素インデックスを頻繁に参照する必要がある場合は、インデックス付きバージョンの方が適切です。
一般に、アルゴリズムは反復子を使用し、反復子の型を変更することで動作を制御 (および暗黙的に文書化) できるため、反復子の使用が推奨されます。反復子の代わりに配列の場所を使用できますが、構文上の違いが目立ちます。
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_matrixs[i]」のようなものを、ランダムな「anim_matrix」という名前のイテレータに省略したくありません。これは、この値がどの配列から生成されたのかを明確に確認できないためです。
本当に、それだけです。どちらの方法でも平均してより簡潔になるというわけではありません。簡潔にすることが本当に目標である場合は、いつでもマクロに頼ることができます。
コンテナの独立性のために
私のアプリケーションの多くは「サムネイル画像の表示」のようなものを必要とするため、私は常に配列インデックスを使用します。だから私はこのようなものを書いた:
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;
}
}
どちらの実装も正しいですが、私は「for」ループの方が好きです。他のコンテナーではなくベクターを使用することにしたので、インデックスを使用するのが最善の選択肢です。ベクトルでイテレータを使用すると、アクセスを容易にする連続したメモリ ブロックにオブジェクトを配置する利点が失われます。
「CPUに何をすべきかを伝える」(必須)よりもさらに優れているのは、「必要なものをライブラリに伝える」(機能的)ことです。
したがって、ループを使用する代わりに、stl に存在するアルゴリズムを学ぶ必要があります。