ストラウストラップのプログラミング入門(35) 20章「コンテナとイテレータ」

ストラウストラップのプログラミング入門』を読む。今日は20章です。STLが登場しました。

20章は、「STLに用意された様々なコンテナの一端を知ること」と、「なぜ様々なコンテナが用意されているかを理解すること」の2つがテーマです。(たぶん・・・)

コンテナ

コンテナの例として、vectorとlistが挙げられ、その特徴が解説されました。特に、listについては、LinkedListの利点である、コンテナの途中への要素の追加が低コストであることがフォーカスされていました。

が、結論としては、特に理由がなければvetorを使えばいいようです。

STL

STLは一般化された解決策を用意し、ユーザは一貫性のある操作でそれを利用できます。ひいては、コードを書く人間が考えることの量を減らしてくれるともいえます。素晴らしいですね。

しかし、要は規約ですので、慣習を覚えるまでは、恩恵に気づかなさそうです。知識がなければ、一般化する発想ができず、発想がなければ、規約を踏まえたものを作ることもできません。

そんな話を内輪でしたところ、「人様の作ったものを使っているうちに覚えるんじゃない?」という話になりました。そりゃそうですね。

20章ドリル

ドリルでは、int型の組込みの配列、vetor、listを扱います。

ドリル6、7: iteratorを規約とする関数で、配列も他のコンテナも同様に扱う

まず、ドリル6では、[f1, e1) を [f2, f2+(e1-f1)) にコピーして、f2+(e1-f1) を返す関数を作ります。

template<class Iter1, class Iter2> Iter2 copy0(Iter1 f1, Iter1 e1, Iter2 f2)
{
    if(f1 == e1) { return f2; }
    Iter2 cmp = f2 + (e1 - f1);
    for(Iter1 ite = f1; ite != e1; ++ite){
        *f2 = *ite;
        if(f2 != cmp){
            ++f2;
        } else {
            break;
        }
    }
    return f2;
}

(・・・こうかな?)

次に、ドリル7で、「配列をvectorに、listを配列にコピーしろ」といわれます。

イテレータを渡す関数のつもりで作っていたら、配列とは、、、。

しかし、配列要素の先頭と末尾の1つの後のアドレスを渡すと、問題なく動きました。

int main()
{
    int ai[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

    vector<int> vi(10);
    for(int i = 0; i < 10; ++i){
        vi[i] = 3 + i;
    }

    list<int> li(0);
    for(int i = 0; i < 10; ++i){
        li.push_back(5 + i);
    }

    copy(&ai[0], &ai[10], vi.begin());	// ★コレ
    copy(li.begin(), li.end(), &ai[0]);
}

範囲外の要素を引数に渡すのは違和感がありますが、イテレータと使い勝手が同じなのはベンリです。これでいいのだろうか。。。 追記: 2012/01/26)C言語の規格上、この使い方で問題ないそうです。E_Mattsanさんに教えていただきました。(規格引用+解説はコメント欄を参照ください)

ドリル8: vectorとlistをiteratorで扱い、添え字を取り出す

vectorは、戻り値のiteratorから、vector::iterator.begin()を引くことで、添え字(index)に当たる値が取り出せます。

int main()
{
    // { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 } の vector<int> を作成
    vector<int> hoge(10);
    for(int i = 0; i < hoge.size(); ++i){
        hoge[i] = i + 3;
    }
    
    int t1 = 3;
    vector<int>::iterator hoge_ite = find(hoge.begin(), hoge.end(), t1);
    
    // 位置を表示
    if(hoge_ite == hoge.end()){
        cout << t1 << " is not found." << endl;
    } else {
        cout << t1 << " is at " << hoge_ite - hoge.begin() << endl;
    }
}

しかし、listでは同じことができないので、困りました。

仕方がないので、begin()と同じ値になるまで、iteratorを減じながらカウントしました。

int main()
{
    // { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 } の list<int>を用意
    list<int> moga(0);
    for(int i = 0; i < 10; ++i){
        moga.push_back(i + 5);
    }
    
    int t2 = 27;
    list<int>::iterator moga_ite = find(moga.begin(), moga.end(), t2);

    // 位置を表示
    if(moga_ite == moga.end()){
        cout << t2 << " is not found." << endl;
    } else {
        /* ☆distanceを使うことにしたため、削除
        int c = 0;
        while(moga_ite != moga.begin()){
            --moga_ite;
            ++c;
        }
        cout << t2 << " is at " << c << endl;
        */

        cout << t2 << " is at " << distance(moga.begin(), moga_ite) << endl; // ★追記
    }
}

一般的な書き方は分かりません。 追記: 2012/01/26)std::distanceを使うそうです。りょーさんにコメントで教えていただきました。

おまけ: 誤植?

ドリル6の関数宣言が、こうなっています。

template<class Iter1, class Iter2> Iter2 copy(Iter f1, Iter1 e1, Iter2 f2);

第1引数の型は、Iter1が正しいのではないでしょうか。(原書のエラッタにはないですが)

20章は、そんな感じでした。