C++11あなたは本当に反復器(iterator)を使うことができますか?

5134 ワード

C++STLは豊富な標準コンテナ(Container)オブジェクト(vector,array,queue,list,set,unordered_map/set...)を提供し、必要に応じて異なるコンテナを選択して様々なタイプのデータを管理することができます.コンテナの使用といえば、反復器(iterator)を使用しないことは不可能であり、すべてのコンテナオブジェクトは、コンテナの特徴に応じて類似しているが異なるiteratorを提供し、コンテナ内のデータにアクセスするために使用されます.
反復器サイクル
一般的に、1つのコンテナ内のすべてのデータを巡回する場合、プログラマーが最もよく使う書き方は次のとおりです.
#include 
#include 
int main(){
list<int> lst;
for(list<int>::iterator itor=lst.begin();itor!=lst.end();itor++){
    cout<//do something
}
}

範囲ベースforサイクル
C++11は、forループに関する新しい特性を提供します.範囲ベースのforループ(the range-base for statement)、さらに「タイプ導出」特性を加えると、上記のコードをさらに簡略化できます.
for(auto &node:lst){
    cout<//do something
}

違いはありませんか?
明らかに、新しいforループの書き方はもっと簡潔ですが、新しいforループの書き方の長所はそれだけですか?よく考えてみると、最初の書き方は、ループするたびにlst.end()を呼び出すことに気づきます.
これはlistです.end()関数のソースコード(C++11のヘッダファイルstl_list.hから):
      /** * Returns a read/write iterator that points one past the last * element in the %list. Iteration is done in ordinary element * order. */
      iterator
      end() _GLIBCXX_NOEXCEPT
      { return iterator(&this->_M_impl._M_node); }

end()関数を呼び出すたびにiteratorオブジェクトが返され、反復ループ全体でend()を呼び出すたびに返されるオブジェクトは実際には完全に同じであり、呼び出されるたびにオブジェクト構造、コピーが発生することは避けられないことがわかります.などの動作は、高性能を要求する応用の場合、このような無意味な繰り返しは受け入れられない.
では、範囲ベースのforループ(the range-base for statement)は同じ処理方式ではないでしょうか.この問題を検証するために、私の前の文章「C++11カスタムコンテナのために標準を実現するforward反復器」で、カスタムハッシュテーブル(HashTableAbstract)に基づく標準forward反復器を実現しました.そこでHashTableAbstractのend()関数にデバッグコードを追加し、end()のたびにデバッグ情報を出力しました.
    iterator end()noexcept
    //{ return iterator(this->m_table,this->m_table.capacity); }//   
    {
        cout << "return a end iterator" << endl;//      
        return iterator(this->m_table, this->m_table.capacity);
    }

次に、次のテストコードを実行します.
    HashSetCl<int> testhash;//HashSetCl   HashTableAbstract  ,      
    testhash.insert(2000);//  3   
    testhash.insert(65535);
    testhash.insert(12345);
    cout<<"testing for statement using iterator:"<//       
    for (auto itor = testhash.begin(); itor != testhash.end(); itor++) {
        cout << *itor << endl;
    }
    cout<<"testing for the range-base for statement:"<//       for  
    for (auto n : testhash) {
        cout << n << endl;
    }

以下はプログラム出力(debug/release結果同様)
testing for statement using iterator://注、ループで3回end()return a end iterator 2000 return a end iterator 12345 return a end iterator 65535 return a end iterator testing for the range-base for statement://注、ループで1回end()return a end iterator 2000 12345 65535
まとめ
以上の出力は、範囲ベースのforループ(the range-base for statement)がループ開始時に1回だけend()を呼び出すことが確認され、一般的に反復器(iterator)を直接使用するループに比べて、コードが簡潔であるという利点だけでなく、性能にも優れている.もちろんこの結論は無秩序コンテナ反復遍歴(読み取り専用)の場合にのみ有効であり(無秩序コンテナはforward反復器のみを提供する)、ランダムアクセス反復器(random-access iterator)を備えたコンテナ(例えばvector,array)、直接下付きでアクセスするのが最も速い.
もしあなたがまだ「伝統を堅持する」場合、反復器を直接使用することに慣れている場合は、コードをいくつか改善することをお勧めします.また、最初のコードを例にとると、ループの開始時にend()関数を呼び出して一時変数endに保存し、ループの比較のたびにend()関数を呼び出すのではなく、一時変数endと直接比較します.end()の繰り返し呼び出しが回避される.
for(auto itor=lst.begin(),end=lst.end();itor!=end;itor++){
    cout<//do something
}