二、C++反復器の二つの実現方式(Range forとC#、Javaのforeach)


一、反復器の概要
このタイトルは、標準ライブラリでのC++の反復器の実装方法が1つしかないため、クラスにbegin()とend()関数を定義し、C++11にrange for文を追加し、反復器の要素を遍歴することができます.反復器を実現する第2の方法は、C++でC#とJavaの反復器モードをシミュレートし、独自のforeach文を定義することです.このほかにも、反復器には多くの実装方法があり、各ライブラリにも独自の反復器の実装が定義されていることが多く、ここでは反復器の本質的な意味を理解すればよい.
反復器は、カーソルとも呼ばれ、内部がどのように実現されているかを知ることなく、コンテナ内の要素にアクセスするためにインクリメント(または次を選択)することができる設計モードです.
多くの言語ではforeach文がコンテナ内の各要素にアクセスするために提供されていますが、実際にはコンテナ内の反復器が呼び出され、抽象的には次のようになります.
foreach(エレメント:コンテナ){...}
上のコードは次のようなものです.
for(カーソル=反復器の先頭を取得し、反復器の末尾を取得します;カーソル!=反復器の末尾;カーソルが単位を移動します){...}//C++の反復モード
または、
while(反復カーソル移動単位(次の単位が存在するかどうかを返す){...}//C#、Javaの反復器モードで、反復器を使うことができます.Currentのようなメソッドは、カーソルが指す要素を返します.
二、C++における反復器の実現方式
反復器はクラスです.反復器をカスタマイズするには、反復器を再ロードします!=、参照(*)、++演算子を解除してrange for文で使用します.range forは、C++11に追加された文です.たとえば、セットに対して文for(auto i:collection)を使用する場合、その意味は次のとおりです.
for ( auto __begin = collection.begin(), auto __end = collection.end(); __begin != __end(); ++__begin ) 
{ i = *__begin; 
	... //    
}

beginとendは集合のメンバー関数であり、反復器を返します.クラスにrange forの操作ができるようにするには、次の必要があります.
・beginとend関数を持ち、それらはすべて反復器を返す
・end関数は、集合の末尾を指す値を返すが、末尾要素を含まない値、すなわち集合範囲で表され、反復器の範囲は[begin,end)左閉右開区間である
反復器の場合、少なくとも以下の要件があります.
・+、!=をリロードする必要があります.和解参照(*)演算子;反復器はポインタのように見えます
・反復器は++で最後に満たされる必要があります!=条件、このようにしてやっと循環を終了することができます
最も簡単な実装コードを以下に示す.CPPCollectionクラスを定義し、文字列の配列があり、range forを通じて各文字列を出力することができます.
#include <iostream>

class CPPCollection {
public:
    class Iterator{
    public:
        int index;
        CPPCollection& outer;
        Iterator(CPPCollection &o, int i) : outer(o), index(i) { }
        void operator++(){
            index++;
        }
        std::string operator*() const{
            return outer.str[index];
        }
        bool operator !=(Iterator i){
            return i.index != index - 1;
        }
    };
public:
    std::string str[10] {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"};
    Iterator begin() {
        return Iterator(*this, 0);
    }
    Iterator end() {
        return Iterator(*this, 9);
    }
};

内部のネストされたクラスIteratorを定義し、+、*、!=演算子.C++の内部ネストされたクラスは周辺のクラスに関連付けられていないため、外部クラスオブジェクトの値にアクセスするために、参照(またはポインタ、この例では参照)を入力する必要があります.Iteratorの自己増加方法は、内部のインデックス値を増加させることです.判断!=の方法は、他の反復器と比較することです.この反復器は一般的に集合の末尾であり、インデックス値が末尾のインデックス値-1に等しくない場合です([begin,end]))の場合、反復器はすでに末尾に達していると考えられます.
CPPCollectionクラスでは、begin()とend()がそれぞれ先頭を返し、反復器を終了するように定義され、次のコードが呼び出されます.
    CPPCollection cpc;
    for (auto i : cpc){
        std::cout << i << std::endl;
    }
では、コレクション内のすべての要素を巡回できます.
汎用アルゴリズムでは、集合の各要素を操作するために、集合の反復ヘッダ、反復ヘッダ、およびstd::find_if(vec.begin(), vec.end(), ...),この汎用アルゴリズムは,反復器の先頭で反復を繰り返し,対応する動作を実行することである.
三、アナログC#(Java)における反復器の実現方式
C#とJavaの反復器の実現方式は比較的に似ていて、ここではC#を例にして、私はC++でその実現をシミュレートします.C#では、反復可能なクラスはIEnumerableインタフェースを継承し、メソッドGetEnumeratorを宣言し、反復器を返します.反復器はIEnumeratorインタフェースを継承し、インタフェースはMoveNext()メソッドを定義します.カーソルを1単位移動し、移動できるかどうかを返します.Reset()メソッド:カーソル帰位;Currentプロパティ:現在のカーソルが指す値を返します(C#では、Currentはプロパティです.これをシミュレートするために、C++で関数として定義されます).抽象関数のみを含むクラスでインタフェースをシミュレートします.
#define interface struct
template <typename T>
interface IEnumerator {
    virtual T& Current() = 0;
    virtual bool MoveNext() = 0;
    virtual void Reset() = 0;
    virtual ~IEnumerator<T>() { };
};


template <typename T>
interface IEnumerable {
    virtual IEnumerator<T>& GetEnumerator() = 0;
    virtual ~IEnumerable () { };
};

foreachのキーワードをカスタマイズするには、マクロ定義を使用して反復器の等価な代替を行います.
#define foreach(item, collection)                                           \
auto &__item_enumerator = collection.GetEnumerator();                       \
__item_enumerator.Reset();                                                  \
while (item = __item_enumerator.Current(), __item_enumerator.MoveNext())

foreachを使用すると、マクロ展開は自動的に集合クラスcollectionのGetEnumerator()関数を呼び出して反復器の参照を取得し、リセットし、現在の値をitemに付与し、反復器は前方に移動します.コードで定義を繰り返すことはできません.item_Enumerator変数であり、タイプがauto&:auto変数の場合、参照シンボルは保持されず、参照(またはポインタ)でない場合、コピーコンストラクション関数がトリガーされ、オブジェクトの多様性が失われます.
先ほどのように、集合クラスを定義します.
class CSharpCollection : public IEnumerable<std::string>{
public:
    class Enumerator : public IEnumerator<std::string> {
    public:
        CSharpCollection &outer;    //   
        int index = 0;  //  
        Enumerator (CSharpCollection &o) : outer(o){
        }
        bool MoveNext() override {
            index++;
            if (index <= 10) return true;
            delete this;
            return false;
        }
        void Reset() override {
            index = 0;
        }
        std::string &Current() override {
            return outer.str[index];
        }
        ~Enumerator (){
            std::cout << "Enumerator    " << std::endl;
        }
    };
public:
    std::string str[10] {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"};
    IEnumerator<std::string>& GetEnumerator() override{
        return *new Enumerator(*this);
    }
};
注意しなければならないのは、多態性を表す関数であれば、戻り値は参照またはポインタでなければならず、スタック内の一時変数ではないため、GetEnumerator()を呼び出した後、生成された反復器を削除し、削除されたコードはMoveNext()内に書かれ、カーソルが移動できない場合、反復器が削除されることである.
その後、独自のforeachマクロ定義で要素を巡ることができます.
    std::string a;
    CSharpCollection csc;
    IEnumerable<std::string>& refcsc = csc;
    foreach (a , refcsc ){
        std::cout << a << std::endl;
    }

上記のコードの3行目は、1つのクラスにIEnumerableクラスが継承されている場合、反復可能であるに違いありません.Reset()、MoveNext()、Current()を呼び出したり、さっき書いたforeachで遍歴したりすることができます.