LoginSignup
3

More than 5 years have passed since last update.

MFC の CList も拡張 for 文で回す

Posted at

概要

これは「以前 MFCの CArray や CStringArray を拡張 for 文で回す という記事を書いたので、じゃあ次は CList を回したいよね!」という記事です。

std::beginstd::end の適切な実装を与えてあげるという方針は CArrayCStringArray と同じですが、 CList の場合は若干コードが複雑です。

最終的なコード

まずヘッダかどこかに以下のようなコードを書きます。

ヘッダかどこか見えるところ
template <typename T>
struct CListIterator
{
    CList<T>* _Cont = nullptr;  // コンテナへのポインタ
    POSITION _pos = NULL;       // 現在のリスト要素位置を指すポジション
    T* _data = nullptr;         // 現在のリスト要素へのポインタ

    // std::end で使うコンストラクタ
    CListIterator() { }

    // std::begin で使うコンストラクタ
    explicit CListIterator(CList<T>& Cont)
    {
        _Cont = &Cont;
        _pos = _Cont->GetHeadPosition();
    }

    // ++ 演算子定義。ダミーで実際には何もしない。
    CListIterator& operator ++ ()
    {
        return *this;
    }

    // 参照演算子。CListのインターフェースとの兼ね合いでこの中で次の要素位置を取得している
    T& operator * ()
    {
        _data = &_Cont->GetNext(_pos);
        return *_data;
    }

    // for ループの終わりをチェックするための比較演算子
    bool operator == (const CListIterator<T>& other) const { return this->_pos == other._pos; }
    bool operator != (const CListIterator<T>& other) const { return !((*this) == other); }
};

namespace std {

  // CList 用 std::begin
  template<typename T>
  CListIterator<T> begin(CList<T>& _Cont)
  {
      return CListIterator<T>(_Cont);
  }

  // CList 用 std::end
  template<typename T>
  CListIterator<T> end(CList<T>& _Cont)
  {
      return CListIterator<T>();
  }
}

上記のイテレータと begin, end が定義されていると拡張 for 文で CList が回せます。

main関数内
CList<CString> clist;
clist.AddTail(_T("hello"));
clist.AddTail(_T("world"));

wcout.imbue(std::locale(""));

// CList<CString> を拡張 for で回す
for (CString& s : clist) { wcout << (LPCTSTR)s << endl; }
コンソール出力
hello
world

なぜこれで for が回るのかの解説

VC++ のコンパイラでは拡張 for 文は以下のように別のコードの短縮形と見なされているような気がします(*ちゃんと確認してない^-^)。

拡張forコードがこうだとすると
for(auto& element : Cont)
{
  // do something
}
このようなコードとして解釈される
for(auto it = std::begin(Cont); it != std::end(Cont);++it)
{
  auto& element = *it;

  // do something
}

上記の理解が正しいとすると、あるコンテナ Cont で拡張 for 文を使えるようにするには最低限以下の仕様を満たせば良いことになります。

  • Cont を引数に取るグローバル関数 'std::begin', 'std::end' を実装する。
  • begin, end の戻り値の型は何でも良いが、その型は ++ 演算子、 != 演算子、 * 演算子を実装している。
  • begin の戻り値に ++ を繰り返していると end の戻り値と同じ値になる(末尾を保証)。

この方針に基づき CListIterator という構造体を作って ++, !=, * 演算子を実装し、内部的な帳尻を合わせたら上手く for が回せました。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3