4
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

自作クラスで簡単に拡張 for 文を使える例いくつか

概要

自作クラスで少しのコードで拡張 for 文(イテレータ)を使える例をいくつかまとめます。
自作クラスでイテレータというとなんだか自分で1から実装しなくちゃいけない気がして憂鬱ですが、実際は以下のパターンのどれかが使える場合が案外多く、少しのコード追加だけで拡張 for 文やSTLのアルゴリズムが使えるようになるので便利です。

(なお、以下の例では全体的に std::vector を使って書いていますが、 std::list, std::map などでも同じですので適宜読み替えて下さい)

修正履歴

  • 2017/05/15 : 「STL コンテナを public 継承した場合」セクションのコードミスを修正しました。

STL コンテナを public 継承した場合

STL コンテナを public 継承した場合には、特に何もしなくても親クラスのイテレータがそのまま使えるので拡張 for 文が使えます。ただし、 STL コンテナのデストラクタには virtual がついていないので取り扱いには注意(*)が必要です。よく継承でなくコンポジションを使うようにと推奨されています。

(*) 例えば親クラスのポインタにアップキャストして delete すると子クラスのデストラクタが呼ばれないなど。

コード例
#include <iostream>
#include <vector>
using namespace std;

using VectReal = std::vector<double>;

class MyVect : public VectReal
{
public:
    MyVect(std::initializer_list<double> list)
        : VectReal(list)
    { }
};

int main(void)
{
    MyVect v = { 1, 2, 3, 4, 5 };
    for (double d : v) { cout << d << " "; }
}
コンソール出力
1 2 3 4 5

STL コンテナを private 継承した場合

private 継承した場合、親クラスのメンバはすべて private になるため、そのままでは拡張 for 文は使えません。ですが下記のように必要なメンバの using 宣言を追加することで親クラスのイテレータや begin, end を使い回すことできます。

コード例
#include <iostream>
#include <vector>
using namespace std;

using VectReal = std::vector<double>;

class MyVect : private VectReal
{
public:
    /* 親クラスのイテレータを使うために iterator, const_iterator, begin, end を using する */
    using VectReal::iterator;
    using VectReal::const_iterator;
    using VectReal::begin;
    using VectReal::end;
    // ...その他使いたいメソッド・メンバ・typedef を using する

    MyVect(std::initializer_list<double> list)
        : VectReal(list)
    { }
};

int main(void)
{
    MyVect v = { 1, 2, 3, 4, 5 };
    for (double d : v) { cout << d << " "; }
}
コンソール出力
1 2 3 4 5

STL コンテナをコンポジションした場合

STL コンテナをコンポジション(メンバ変数として保持)した場合も、そのままでは拡張 for 文は使えません。しかしコンポジションしたメンバ変数に対して begin, end を委譲することでイテレータを使い回すことができます。

コード例
#include <iostream>
#include <vector>
using namespace std;

using VectReal = std::vector<double>;

class MyVect
{
public:

    MyVect(std::initializer_list<double> list)
        : m_vect(list)
    { }

    // iterator, const_iterator の typedef をしておくと MyVect::iterator と書けて便利(必須ではない)。
    using iterator = VectReal::iterator;
    using const_iterator = VectReal::const_iterator;

    // m_vect に処理を委譲
    iterator begin() { return m_vect.begin(); }
    iterator end() { return m_vect.end(); }
    const_iterator begin() const { return m_vect.begin(); }
    const_iterator end() const { return m_vect.end(); }


private:
   // コンポジション
   VectReal m_vect;
};

int main(void)
{
    MyVect v = { 1, 2, 3, 4, 5 };
    for (double d : v) { cout << d << " "; }
}
コンソール出力
1 2 3 4 5

ポインタ配列をメンバに持つ場合

ポインタ配列をメンバに持つ場合にも、①ポインタをイテレータとして typedefして、② begin, end を下記のように実装すれば簡単に拡張 for 文が使えます。 rbegin, rend も同じ要領で実装できます。

コード例
#include <iostream>
#include <vector>
using namespace std;

class MyVect
{
public:

    MyVect(std::initializer_list<double> list)
    {
        m_size = list.size();
        m_vect = new double[m_size];
        std::copy(list.begin(), list.end(), m_vect);
    }

    using iterator = double*;
    using const_iterator = const double*;

    iterator begin() { return m_vect; }
    iterator end() { return m_vect + m_size; }
    const_iterator begin() const { return m_vect; }
    const_iterator end() const { return m_vect + m_size; }


private:
   double* m_vect;
   size_t m_size;
};

int main(void)
{
    MyVect v = { 1, 2, 3, 4, 5 };
    for (double d : v) { cout << d << " "; }
}

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
4
Help us understand the problem. What are the problem?