読者です 読者をやめる 読者になる 読者になる

C++と色々

主にC++やプログラムに関する記事を投稿します。

範囲for文を自作クラスで使う

範囲for文とは

C++11に範囲for文がある。VisualStudioでは2012から使えるようになった。
これは、STLのコンテナや、組み込み配列をfor文で最初から最後までの要素を処理する時に、従来のfor文でいちいち「最初から、終わりまで」という範囲を書かなくて済むというもの。

#include <iostream>

int main()
{
    int a[3] = {1, 2, 3};

    //今まで(C++03) : 配列の範囲を指定しなければならなかった
    for(int i = 0; i < 3; ++i)
    {
        std::cout << a[i] << std::endl;
    }

    //範囲for文(C++11) : 配列の最初から最後まで操作することが自明
    for(int it : a)
    {
        std::cout << it << std::endl;
    }
}

この例でイメージはつかめたはず。また、上記の範囲for文は型推論を使ってこう書いてもいい。

for(auto it : a)
{
    std::cout << it << std::endl;
}

STLのコンテナにも使える

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);

    //C++03
    for(std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it)
    {
        std::cout << *it;
    }

    //C++11
    for(std::vector<int>::value_type it : vec)
    {
        std::cout << it;
    }
}

型名が長くタイピングが大変だし、可読性も下がるので型推論を使ったほうが良い

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);

    //C++03
    for(auto it = vec.begin(); it != vec.end(); ++it)
    {
        std::cout << *it;
    }

    //C++11
    for(auto it : vec)
    {
        std::cout << it;
    }
}

範囲for文を自作クラスで使えるようにする

ここからが本題。この範囲for文を自作クラスで使うにはどのような要件を満たせばいいのだろうか?

答えは、イテレータを戻す、beginと言う名前のメンバ関数と、endと言う名前のメンバ関数がアレば良い。
ここで言うイテレータとは間接参照演算子と前置インクリメント演算子と不等価演算子の3つが定義されたクラスである*1
つまり、「間接参照演算子と前置インクリメント演算子と不等価演算子の3つが定義されたクラス、のインスタンスを返すbeginとendという名前のメンバ関数を持つ」クラスなら範囲for文を使用出来る。

上の要件を満たせば、どんなにふざけた演算子のオーバーロード等をしていてもの範囲for文を使うことができる。

#include <iostream>

//間接参照演算子と前置インクリメント演算子と不等価演算子を定義する
class hoge_iterator
{
public:
    hoge_iterator() : count_(0)
    {
    }

    int operator *()
    {
        return count_;
    }

    void operator ++()
    {
    }

    bool operator !=(hoge_iterator&)
    {
        return ++count_ < 5;
    }

private:
    int count_;
};

//イテレータを返すbeginとendという2つのメンバ関数を定義する
class h
{
public:
    hoge_iterator begin() const
    {
        return hoge_iterator();
    }
    hoge_iterator end() const
    {
        return hoge_iterator();
    }
};

int main()
{
    h h_;

    //範囲for文が使える!
    for(auto it : h_)
    {
        std::cout << it << "\n";
    }
}

実行結果

1
2
3
4
続行するには何かキーを押してください . . .

*1:本来ならコンテナの要素への統一したアクセス方法を実現するデザインパターンで、イテレータの意味を考えて実装するべきだが、今回はあくまで自作クラスが範囲for文で動けば良いので、こういう書き方をした