12

これはつまらないことのように思えるかもしれませんが、STL コンテナーを反復処理する次の 2 つの方法のうち、どちらが優れているでしょうか? なぜですか?

class Elem;
typedef vector<Elem> ElemVec;
ElemVec elemVec;

// Method 0
for (ElemVec::iterator i = elemVec.begin(); i != elemVec.end(); ++i)
{
    Elem& e = *i;
    // Do something
}

// Method 1
for (int i = 0; i < elemVec.size(); ++i)
{
    Elem& e = elemVec.at(i);
    // Do something
}

方法 0 はよりクリーンな STL のように見えますが、方法 1 はより少ないコードで同じことを達成します。コンテナーに対する単純な反復は、ソース コードのいたるところに見られるものです。したがって、私は方法 1 を選択する傾向があります。方法 1 は視覚的な混乱とコード サイズを削減すると思われます。

PS: イテレータは単純なインデックスよりもはるかに多くのことができることを知っています。ただし、返信/ディスカッションは、上記のようなコンテナーに対する単純な反復に集中してください。

4

9 に答える 9

16

最初のバージョンは、任意のコンテナーで動作するため、任意のコンテナーをパラメーターとして受け取るテンプレート関数でより役立ちます。また、ベクターの場合でも、わずかに効率的であると考えられます。

2 番目のバージョンは、ベクターおよびその他の整数インデックス付きコンテナーに対してのみ機能します。これらのコンテナーではやや慣用的であり、C++ の初心者が簡単に理解でき、インデックスを使用して何か他のことを行う必要がある場合に役立ちますが、これは珍しくありません。

いつものように、「こっちの方がいい」という単純な答えはありません。

于 2009-04-04T08:30:21.293 に答える
8

(非常に?) わずかな効率の低下を気にしない場合は、Boost.Foreachの使用をお勧めします。

BOOST_FOREACH( Elem& e, elemVec )
{
    // Your code
}
于 2009-04-04T08:38:30.383 に答える
6

方法 0 の方が高速であるため、推奨されます。

方法 1 では、コンテナーと stl の実装に応じて、O(1) が許可されている size() を使用します。

于 2009-04-04T08:59:00.567 に答える
5

標準ライブラリ コンテナーを反復処理する次の方法が最適です。

(およびそれ以降) の範囲ベースのfor-loopauto指定子と共に使用します。

// Method 2
for (auto& e: elemVec)
{
    // Do something with e...
}

これはあなたのと似ていますMethod 0が、タイピングとメンテナンスが少なくて済み、単純な配列を含む and と互換性のある任意のコンテナーで動作std::begin()ます。std::end()

于 2012-04-29T01:09:10.033 に答える
4

方法 0 のその他の利点:

  • ベクターから別のコンテナーに移動しても、ループは同じままです。
  • 必要に応じて iterator から const_iterator に簡単に移動できます。
  • c++0x が登場すると、自動入力によってコードの混乱がいくらか軽減されます。

主な欠点は、多くの場合、2 つのコンテナーをスキャンすることです。この場合、インデックスは 2 つの反復子を保持するよりもクリーンです。

于 2009-04-04T08:35:15.103 に答える
2

偶然にも、私は最近速度テストを行いました (10 * 1024 * 1024 int を rand() で埋めます)。
これらは 3 回の実行で、時間はナノ秒単位です

vect[i] time      : 373611869  
vec.at(i) time    : 473297793  
*it = time        : 446818590  
arr[i] time       : 390357294  
*ptr time         : 356895778  

UPDATE : stl-algorithm std::generate を追加しました。これは、特別なイテレータ最適化 (VC++2008) により、最も高速に実行されるようです。マイクロ秒単位の時間。

vect[i] time      : 393951
vec.at(i) time    : 551387
*it = time        : 596080
generate = time   : 346591
arr[i] time       : 375432
*ptr time         : 334612

結論: 標準アルゴリズムを使用すると、明示的なループよりも高速になる可能性があります。(そしてまた良い習慣)

更新:上記の時間はI / Oバウンドの状況でした。CPUバウンドで同じテストを行いました(キャッシュに繰り返し収まる比較的短いベクトルを繰り返し、各要素を2倍してベクトルに書き戻します)

//Visual Studio 2008 Express Edition
vect[i] time      : 1356811
vec.at(i) time    : 7760148
*it = time        : 4913112
for_each = time   : 455713
arr[i] time       : 446280
*ptr time         : 429595

//GCC
vect[i] time      : 431039
vec.at(i) time    : 2421283
*it = time        : 381400
for_each = time   : 380972
arr[i] time       : 363563
*ptr time         : 365971  

興味深いことに、イテレータと operator[] は for_each と比較して VC++ でかなり遅くなります (これはパフォーマンスのためのテンプレート マジックによってイテレータをポインタに劣化させるようです)。
GCC では、アクセス時間は at() のほうが悪くなりますが、これは正常な動作です。これは、テストで範囲チェックされる唯一の関数であるためです。

于 2009-04-04T09:25:50.287 に答える
2

いくつかの理由から、方法 0。

  • 意図をより適切に表現し、コンパイラの最適化と読みやすさを支援します
  • オフバイワンエラーの可能性が低い
  • vector を operator[] を持たないリストに置き換えても機能します

もちろん、多くの場合、最善の解決策は解決策 2 です。標準アルゴリズムの 1 つです。std::for_each、std::transform、std::copy、またはその他必要なもの。これにはさらにいくつかの利点があります。

  • それはあなたの意図をよりよく表現し、いくつかの重要なコンパイラの最適化を可能にします (MS のセキュア SCL はメソッド 0 と 1 で境界チェックを実行しますが、std アルゴリズムではそれをスキップします)
  • コードが少なくなります (少なくとも呼び出しサイトでは。もちろん、ループ本体を置き換えるためにファンクターなどを作成する必要がありますが、使用サイトではコードがかなりクリーンアップされます。これがおそらく最も重要な部分です)。 .

一般に、コードを過度に指定しないでください。やりたいことを正確に指定し、それ以外は何も指定しないでください。std アルゴリズムは通常そこに行く方法ですが、それらがなくても index が必要ない場合はi、なぜそれを持っているのでしょうか? その場合は、代わりに反復子を使用してください。

于 2009-04-04T11:11:58.503 に答える
1

コンテナの種類によって異なります。の場合、vectorおそらくどちらを使用してもかまいません。メソッド 0 はより慣用的なものになりましたが、誰もが言うように、それらは大きな違いではありません。

代わりに ,を使用することにした場合list、方法 1 は原則として になりますO(N)が、実際には listat()メソッドがないため、そのようにすることもできません。(したがって、あるレベルで、あなたの質問はデックを積み上げます。)

しかし、それ自体がメソッド 0 のもう 1 つの引数です。異なるコンテナーに対して同じ構文を使用します。

于 2009-04-04T09:20:47.053 に答える