web-dev-qa-db-ja.com

C ++のfor eachループ関数をカスタムクラスで動作させる方法

C/C++プログラミングは初めてですが、C#で1.5年間プログラミングを続けています。私はC#が好きで、Listクラスが好きなので、演習としてC++でListクラスを作成することを考えました。

_List<int> ls;
int whatever = 123;
ls.Add(1);
ls.Add(235445);
ls.Add(whatever);
_

実装は、既存のArray Listクラスに似ています。アイテムを格納する_T* vector_メンバーがあり、このストレージが完全にいっぱいになると、サイズを変更します。

これは本番環境では使用しないでください。これは演習にすぎないことに注意してください。 _vector<T>_と友達をよく知っています。

次に、リストの項目をループします。 for(int i=0;i<n; i==)を使用したくない。ビジュアルスタジオでforと入力し、Intellisenseを待っていましたが、次のように提案されました。

_for each (object var in collection_to_loop)
{

}        
_

これは明らかに私のList実装では機能しません。マクロマジックを実行できると考えましたが、これは大きなハックのように感じます。実際、私が一番気になるのは、そのような型を渡すことです。

_#define foreach(type, var, list)\
int _i_ = 0;\
##type var;\
for (_i_ = 0, var=list[_i_]; _i_<list.Length();_i_++,var=list[_i_]) 

foreach(int,i,ls){
    doWork(i);
}
_

私の質問は、このカスタムリストクラスを_foreach-like_ループで動作させる方法はありますか?

32
Ricardo Pieper

まず、_for-each_の_C++_ループの構文は_C#_とは異なります(_range based for loop_とも呼ばれます。形式は次のとおりです。

_for(<type> <name> : <collection>) { ... }
_

たとえば、_std::vector<int> vec_の場合、次のようになります。

_for(int i : vec) { ... }
_

カバーの下で、これは、イテレータを返すbegin()およびend()メンバー関数を効果的に使用します。したがって、カスタムクラスで_for-each_ループを利用できるようにするには、begin()およびend()関数を提供する必要があります。これらは通常オーバーロードされ、iteratorまたは_const_iterator_を返します。反復子の実装は難しい場合がありますが、ベクトルのようなクラスではそれほど難しくありません。

_template <typename T>
struct List
{
    T* store;
    std::size_t size;
    typedef T* iterator;
    typedef const T* const_iterator;

    ....

    iterator begin() { return &store[0]; }
    const_iterator begin() const { return &store[0]; }
    iterator end() { return &store[size]; }
    const_iterator end() const { return &store[size]; }

    ...
 };
_

これらを実装すると、上記の範囲ベースのループを利用できます。

43
Yuushi

iterableをタイプIterableとします。次に、作るために

_for (Type x : iterable)
_

コンパイル、TypeおよびITypeと呼ばれる型がなければならず、関数がなければなりません

_IType Iterable::begin()
IType Iterable::end()
_

ITypeは機能を提供する必要があります

_Type operator*()
void operator++()
bool operator!=(IType)
_

全体の構造は本当に洗練された構文糖のようなものです

_for (IType it = iterable.begin(); it != iterable.end(); ++it) {
    Type x = *it;
    ...
}
_

Typeの代わりに、互換性のある型(_const Type_や_Type&_など)を使用できます。これには、予想される影響(安定性、コピーの代わりの参照など)が含まれます。 )。

展開全体が構文的に行われるため、演算子の宣言を少し変更することもできます。 * itに参照を返させるか、!=に必要に応じて_const IType& rhs_を取得させます。

_*it_が参照を返さない場合、for (Type& x : iterable)形式を使用できないことに注意してください(ただし、参照を返す場合は、コピーバージョンも使用できます)。

operator++()は_++_演算子のprefixバージョンを定義することにも注意してください-ただし、明示的に定義しない限りpostfix演算子としても使用されます接尾辞_++_。接尾辞_++_のみを指定した場合、ranged-forはコンパイルされません。btw。はoperator++(int)(ダミーint引数)として宣言できます。


最小限の作業例:

_#include <stdio.h>
typedef int Type;

struct IType {
    Type* p;
    IType(Type* p) : p(p) {}
    bool operator!=(IType rhs) {return p != rhs.p;}
    Type& operator*() {return *p;}
    void operator++() {++p;}
};

const int SIZE = 10;
struct Iterable {
    Type data[SIZE];

    IType begin() {return IType(data); }
    IType end() {return IType(data + SIZE);}
};

Iterable iterable;

int main() {
    int i = 0;
    for (Type& x : iterable) {
        x = i++;
    }
    for (Type x : iterable) {
        printf("%d", x);
    }
}
_

出力

_0123456789
_

次のマクロを使用して、ranged-for-each(たとえば、古いC++コンパイラ用)を偽造できます。

_ #define ln(l, x) x##l // creates unique labels
 #define l(x,y)  ln(x,y)
 #define for_each(T,x,iterable) for (bool _run = true;_run;_run = false) for (auto it = iterable.begin(); it != iterable.end(); ++it)\
     if (1) {\
         _run = true; goto l(__LINE__,body); l(__LINE__,cont): _run = true; continue; l(__LINE__,finish): break;\
         } else\
            while (1)   \
                if (1) {\
                    if (!_run) goto l(__LINE__,cont);/* we reach here if the block terminated normally/via continue */   \
                    goto l(__LINE__,finish);/* we reach here if the block terminated by break */\
                }   \
                else\
                l(__LINE__,body): for (T x = *it;_run;_run=false) /* block following the expanded macro */                         

 int main() {
     int i = 0;
     for_each(Type&, x, iterable) {
         i++;
         if (i > 5) break;
         x = i;
     }
     for_each(Type, x, iterable) {
         printf("%d", x);
     }
     while (1);
 }
_

(コンパイラにautoがない場合は、declspecを使用するか、ITypeを渡します)。

出力:

_ 1234500000
_

ご覧のとおり、continuebreakは、複雑な構造のおかげでこれで動作します。カスタム制御構造を作成するためのCプリプロセッサハッキングの詳細については、 http://www.chiark.greenend.org.uk/~sgtatham/mp/ を参照してください。

19
masterxilo

Intellisenseが提案した構文はC++ではありません。または、何らかのMSVC拡張機能です。

C++ 11には、コンテナの要素を反復処理するための 範囲ベースのforループ があります。最初の要素と最後の要素のそれぞれに反復子を返すクラスのbegin()およびend()メンバー関数を実装する必要があります。それはもちろん、クラスにも適切な iterators を実装する必要があることを意味します。本当にこのルートに行きたい場合は、 Boost.IteratorFacade ;をご覧ください。イテレータを自分で実装する際の苦痛の多くを軽減します。

その後、これを書くことができます:

for( auto const& l : ls ) {
  // do something with l
}

また、C++を初めて使用するので、標準ライブラリに複数の container クラスがあることを確認してください。

7
Praetorian

C++の構文には、for_eachループ機能がありません。 c ++ 11を使用するか、テンプレート関数 std :: for_each を使用する必要があります。

#include <vector>
#include <algorithm>
#include <iostream>

struct Sum {
    Sum() { sum = 0; }
    void operator()(int n) { sum += n; }

    int sum;
};

int main()
{
    std::vector<int> nums{3, 4, 2, 9, 15, 267};

    std::cout << "before: ";
    for (auto n : nums) {
        std::cout << n << " ";
    }
    std::cout << '\n';

    std::for_each(nums.begin(), nums.end(), [](int &n){ n++; });
    Sum s = std::for_each(nums.begin(), nums.end(), Sum());

    std::cout << "after:  ";
    for (auto n : nums) {
        std::cout << n << " ";
    }
    std::cout << '\n';
    std::cout << "sum: " << s.sum << '\n';
}
2
saeed

@yngumが示唆しているように、コレクションでbegin()およびend()メソッドを定義してカスタムイテレータを返すことにより、VC++ for each拡張機能を任意のコレクションタイプで動作させることができます。反復子は、必要なインターフェイス(逆参照演算子、増分演算子など)を実装する必要があります。これは、レガシーコードのすべてのMFCコレクションクラスをラップするために実行しました。それは少し作業ですが、行うことができます。

0
Scott Jones