C++ | コレクションとイテレータを自作する

C++11 以降では、 for (auto x : vector) といったモダンな形式で for 文を記述することができます。
for 文の右辺には std::vector などのコレクションクラスのインスタンスを指定します。
これによって、コレクションの先頭から末尾までを、x の参照でループすることができます。

この記事では、自作のコレクションクラスを for 文に利用するためのサンプルを紹介します。

目次

動かしたいコード

自作クラス MyCollection を定義し、このインスタンスを for ループで利用できるよう拡張します。
次のような main 関数を実行できる状態を目指します。

#include <iostream>
#include <vector>

class MyCollection
{
public:
    MyCollection() : v_({3, 1, 4}) {}
    
private:
    std::vector<int> v_;
};

int main()
{
    MyCollection collection;
    for (auto v : collection)
    {
        std::cout << v << std::endl;
    }
    return 0;
}

MyCollection といいつつ、中身は [3, 1, 4] のリストがハードコードされた雑なサンプルです。

MyCollection に begin と end を定義する

前述のコードをそのままビルドすると、当然ながらコンパイルエラーになります。
MyCollection クラスには、for 文でループさせるための準備が何もできていません。

my_collection.cc: In function ‘int main()’:
my_collection.cc:26:19: error: ‘begin’ was not declared in this scope; did you mean ‘std::begin’?
   26 |     for (auto v : collection)
      |                   ^~~~~~~~~~
      |                   std::begin
In file included from /usr/include/c++/11/string:54,
                 from /usr/include/c++/11/bits/locale_classes.h:40,
                 from /usr/include/c++/11/bits/ios_base.h:41,
                 from /usr/include/c++/11/ios:42,
                 from /usr/include/c++/11/ostream:38,
                 from /usr/include/c++/11/iostream:39,
                 from my_collection.cc:1:
/usr/include/c++/11/bits/range_access.h:108:37: note: ‘std::begin’ declared here
  108 |   template<typename _Tp> const _Tp* begin(const valarray<_Tp>&) noexcept;
      |                                     ^~~~~
my_collection.cc:26:19: error: ‘end’ was not declared in this scope; did you mean ‘std::end’?
   26 |     for (auto v : collection)
      |                   ^~~~~~~~~~
      |                   std::end
In file included from /usr/include/c++/11/string:54,
                 from /usr/include/c++/11/bits/locale_classes.h:40,
                 from /usr/include/c++/11/bits/ios_base.h:41,
                 from /usr/include/c++/11/ios:42,
                 from /usr/include/c++/11/ostream:38,
                 from /usr/include/c++/11/iostream:39,
                 from my_collection.cc:1:
/usr/include/c++/11/bits/range_access.h:110:37: note: ‘std::end’ declared here
  110 |   template<typename _Tp> const _Tp* end(const valarray<_Tp>&) noexcept;
      |                                     ^~~

エラーの内容を見ると、begin, end のメソッドが定義されていないような旨が出力されています。

このエラーを解消するために、MyCollection に該当メソッドを定義します。
begin, end メソッド戻り値として Iterator クラスが必要になるため、MyIterator クラスも定義しておきます。

MyIterator にはメンバ n_ を定義し、ここに参照中のインデックスを保持させるようにします。

#include <iostream>
#include <vector>

class MyIterator;

class MyCollection
{
public:
    MyCollection() : v_({3, 1, 4}) {}
    MyIterator begin();
    MyIterator end();

private:
    std::vector<int> v_;
};

class MyIterator
{
public:
    MyIterator(MyCollection &ref, std::size_t n) : ref_(ref), n_(n) {}

private:
    MyCollection &ref_;
    std::size_t n_;
};

MyIterator MyCollection::begin() { return MyIterator(*this, 0); }
MyIterator MyCollection::end() { return MyIterator(*this, v_.size()); }

int main()
{
    MyCollection collection;
    for (auto v : collection)
    {
        std::cout << v << std::endl;
    }
    return 0;
}

MyIterator に各種 operator を定義する

もう一度コンパイルすると、また別のエラーが発生します。
今度は MyIterator クラスに、いくつかの operator オーバーロードの定義が不足している旨のメッセージです。

my_collection.cc: In function ‘int main()’:
my_collection.cc:33:19: error: no match for ‘operator!=’ (operand types are ‘MyIterator’ and ‘MyIterator’)
   33 |     for (auto v : collection)
      |                   ^~~~~~~~~~
my_collection.cc:33:19: error: no match for ‘operator++’ (operand type is ‘MyIterator’)
   33 |     for (auto v : collection)
my_collection.cc:33:19: error: no match for ‘operator*’ (operand type is ‘MyIterator’)
   33 |     for (auto v : collection)
      |                   ^~~~~~~~~~

これに従って、operator オーバーロードを3つ定義します。

#include <iostream>
#include <vector>

class MyIterator;

class MyCollection
{
public:
    MyCollection() : v_({3, 1, 4}) {}
    MyIterator begin();
    MyIterator end();

    friend MyIterator;

private:
    std::vector<int> v_;
};

class MyIterator
{
public:
    MyIterator(MyCollection &ref, std::size_t n) : ref_(ref), n_(n) {}

    bool operator!=(MyIterator &rhs) { return n_ != rhs.n_; }
    MyIterator &operator++()
    {
        ++n_;
        return *this;
    }
    int operator*() { return ref_.v_[n_]; };

private:
    MyCollection &ref_;
    std::size_t n_;
};

MyIterator MyCollection::begin() { return MyIterator(*this, 0); }
MyIterator MyCollection::end() { return MyIterator(*this, v_.size()); }

int main()
{
    MyCollection collection;
    for (auto v : collection)
    {
        std::cout << v << std::endl;
    }
    return 0;
}

!= 演算子は、for 文の終了条件で用いられます。
end() で生成されるインスタンスクラスとの比較が行われます。

++ 演算子は、ループごとのイテレータインクリメントで参照されます。
ここでは前置型(++it の形式)の演算子が必要になります。

*演算子は、値を参照する際に用いられます。

ここまで定義すると、コンパイルが通って main 関数を実行することができるようになります。
あらかじめハードコードした [3, 1, 4] のリストが順に出力されてプログラムを終えます。

$ g++ my_collection.cc && ./a.out
3
1
4

今回は最小限でプログラムを動作させるために、細部はだいぶ雑な記述となっています。
これをベースにクラスを充実させることで、より実用的なコレクションクラスの定義も可能です。

  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次