独自のクラスを C++11 の範囲に基づく for ループに対応させる - C++ プログラミング

PROGRAM


独自のクラスを C++11 の範囲に基づく for ループに対応させる

C++ では イテレータ というものを使って効率良く繰り返し処理が行えるようになっていますが、これを独自クラスにも実装させる方法については 独自のイテレータを実装する のところで紹介しました。

ところでこのイテレータを実装することで、自分で作成したクラスを 配列を繰り返し処理する 3 つの方法 の中で紹介した "C++11 の for 構文(範囲に基づく for ループ)" で使うことができるようになります。

 

たとえば、次のようなクラスがあったとします。

class CMyList

{

private:

std::vector<int> m_items;

 

public:

// イテレータの型を定義しています(今回のテーマに必須ではありません)

typedef std::vector<int>::iterator iterator;

typedef std::vector<int>::const_iterator const_iterator;

 

// コンストラクタです(今回のテーマに必須ではありません)

CMyList();

 

};

このままではまだ C++11 の "範囲に基づく for ループ" には対応していないですけど、それに対応させるには 2 つの実装方法があります。

後に述べるそれらを適用することで、次のようにしてこの独自クラスを "範囲に基づく for ループ" で使用することが可能になります。

CMyList list;

 

// イテレータの型を定義しています(今回のテーマに必須ではありません)

for (auto& item : list)

{

// イテレータが返す値を、ここでひとつひとつ処理できます。

 

}

ちなみにそれらの対応をしていない場合、たとえば Xcode の LLVM コンパイラの場合、for の行で次のコンパイルエラーが発生します。

Invalid range expression of type 'CMyList'; no viable 'begin' function available.

Visual Studio 2013 の場合は、for の行で次のエラーが報告されます。

この範囲ベースの 'for' ステートメントには、適切な "begin" 関数が必要ですが、見つかりません。

このようなエラーも、独自のクラスに少しだけ実装を加えることで簡単に解消できます。

 

方法 1 : クラスに begin 関数と end 関数を実装する

独自のクラスを "C++11 の for 構文(範囲に基づく for ループ)" に対応させるもっとも簡単な方法は、そのクラスにメンバー関数として、範囲の最初と範囲の最後のイテレータを返す関数をそれぞれ "begin" と "end" という名前で実装する方法です。

class CMyList

{

private:

std::vector<int> m_items;

 

public:

typedef std::vector<int>::iterator iterator;

typedef std::vector<int>::const_iterator const_iterator;

 

CMyList();

 

// 範囲の始まりを返す "begin" 関数です。必ずこの名前にする必要があります。

iterator begin();

const_iterator begin() const;

 

// 範囲の終端を返す "end" 関数です。必ずこの名前にする必要があります。

iterator end();

const_iterator end() const;

};

"begin" 関数と "end" 関数のそれぞれで、iterator を返す通常のものと const_iterator を返す const 指定のものとがありますが、これは const 指定のクラスインスタンスが "範囲に基づく for ループ" に指定された場合でも対応できるようにするためです。

イテレータでは読み取り専用としてだけ扱いたい場合は const 指定の方だけで大丈夫です。

 

そしてこの "begin" 関数と "end" 関数を適切に実装します。

今回は単純に、メンバ変数に持っている std::vector のイテレータをそのまま返すことにしています。

CMyList::iterator CMyList::begin()

{

return m_items.begin();

}

 

CMyList::const_iterator CMyList::begin() const

{

return m_items.begin();

}

 

CMyList::iterator CMyList::end()

{

return m_items.end();

}

 

CMyList::const_iterator CMyList::end() const

{

return m_items.end();

}

これだけで、このクラスを C++11 の "範囲に基づく for ループ" で使用できるようになりました。

範囲に基づく for ループでこのクラスのインスタンスを指定すると、この "begin" 関数と "end" 関数の範囲の中から、そのイテレータが示す値を順々に処理してくれるようになります。

 

方法 2 : 同じ名前空間に begin 関数と end 関数を実装する

クラス自身に "begin" 関数と "end" 関数を持たせなくても、同じ 名前空間 上に、そのクラスを引数に取る "begin" 関数と "end" 関数を用意して、それぞれで範囲の始点と末端を示すイテレータを返すようにする方法もあります。

このとき、それらの関数から、扱うクラスのイテレータを取得する必要があるため、クラス内にもイテレータを返す関数を実装する必要がありますが、今回はそれらの関数の名前を自由に決められます。

class CMyList

{

private:

std::vector<int> m_items;

 

public:

typedef std::vector<int>::iterator iterator;

typedef std::vector<int>::const_iterator const_iterator;

 

CMyList();

 

// 範囲の始まりを返す関数です。名前は自由に決められます。

iterator eback();

const_iterator eback() const;

 

// 範囲の終端を返す関数です。名前は自由に決められます。

iterator egptr();

const_iterator egptr() const;

};

 

// 範囲の始まりを返す "begin" 関数です。

CMyList::iterator begin(CMyList& list);

CMyList::const_iterator begin(CMyList& list) const;

 

// 範囲の終端を返す "end" 関数です。

CMyList::iterator end(CMyList& list);

CMyList::const_iterator end(CMyList& list) const;

このように "begin" 関数と "end" 関数を、クラス定義の外側の、それと同じ名前空間上に定義します。

const_iterator と iterator を返す関数をそれぞれ用意しているのは、範囲に基づく for ループに const 指定のクラスインスタンスが渡されたときにも対応できるようにするためです。

また、クラスには、クラスの外で定義した "begin" 関数と "end" 関数では、引数で受け取ったインスタンスのイテレータを返せるように、イテレータを取得する関数(ここでは "eback" 関数と "egptr" 関数)を用意しておきます。

 

クラス定義の外側に宣言した "begin" 関数と "end" 関数は、たとえば次のような実装になります。

CMyList::iterator begin(CMyList& list)

{

return list.eback();

}

 

CMyList::const_iterator begin(CMyList& list) const

{

return list.eback();

}

 

CMyList::iterator end(CMyList& list)

{

return list.egptr();

}

 

CMyList::const_iterator end(CMyList& list) const

{

return list.egptr();

}

このような方法で、クラスを C++11 の "範囲に基づく for ループ" に対応させることができます。

範囲に基づく for ループでこのクラスのインスタンスを指定すると、この "begin" 関数と "end" 関数に指定したインスタンスが引数で渡されて、それで得られた範囲に基づいて、イテレータが示す値を順々に処理されることになります。


[ もどる ]