web-dev-qa-db-fra.com

Comment faire en sorte que la fonction de boucle for each en C++ fonctionne avec une classe personnalisée

Je suis nouveau dans la programmation C/C++, mais je programme en C # depuis 1,5 ans maintenant. J'aime C # et j'aime la classe List. J'ai donc pensé à créer une classe List en C++ en tant qu'exercice.

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

L'implémentation est similaire à n'importe quelle classe Array List. J'ai un membre T* vector où je stocke les éléments, et lorsque ce stockage est sur le point d'être complètement rempli, je le redimensionne. 

S'il vous plaît noter que cela ne doit pas être utilisé dans la production, ce n'est qu'un exercice. Je connais bien vector<T> et mes amis.

Maintenant, je veux parcourir les éléments de ma liste. Je n'aime pas utiliser for(int i=0;i<n; i==). J'ai tapé for dans le studio visuel, attendu pour Intellisense, et il m'a suggéré ceci:

for each (object var in collection_to_loop)
{

}        

Cela ne fonctionnera évidemment pas avec la mise en œuvre de ma liste. Je pensais pouvoir faire de la magie macro, mais cela ressemble à un énorme bidouillage. En fait, ce qui me dérange le plus, c’est de passer le type comme ça:

#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);
}

Ma question est la suivante: existe-t-il un moyen de faire fonctionner cette classe List personnalisée avec une boucle foreach-like?

30
Ricardo Pieper

Tout d'abord, la syntaxe d'une boucle for-each dans C++ est différente de celle de C# (elle est également appelée range based for loop. Elle a la forme suivante:

for(<type> <name> : <collection>) { ... }

Ainsi, par exemple, avec un std::vector<int> vec, ce serait quelque chose comme:

for(int i : vec) { ... }

Sous les couvertures, cela utilise efficacement les fonctions membres begin() et end(), qui renvoient des itérateurs. Par conséquent, pour permettre à votre classe personnalisée d'utiliser une boucle for-each, vous devez fournir une fonction begin() et une fonction end(). Celles-ci sont généralement surchargées et renvoient soit une iterator, soit un const_iterator. Implémenter des itérateurs peut être difficile, bien qu'avec une classe semblable à un vecteur ce ne soit pas trop difficile.

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]; }

    ...
 };

Avec ces implémentations, vous pouvez utiliser une boucle basée sur la plage comme ci-dessus.

41
Yuushi

Soit iterable de type Iterable. Puis, pour faire

for (Type x : iterable)

compiler, il doit y avoir des types appelés Type et IType et il doit y avoir des fonctions 

IType Iterable::begin()
IType Iterable::end()

IType doit fournir les fonctions

Type operator*()
void operator++()
bool operator!=(IType)

Toute la construction est un sucre syntaxique vraiment sophistiqué pour quelque chose comme

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

où au lieu de Type, tout type compatible (tel que const Type ou Type&) peut être utilisé, ce qui aura les implications attendues (constness, référence-à-place-de-copie, etc.). 

Comme tout le développement se fait de manière syntaxique, vous pouvez également modifier un peu la déclaration des opérateurs, par exemple. ayant * le renvoyer une référence ou ayant! = prenez un const IType& rhs si nécessaire.

Notez que vous ne pouvez pas utiliser le formulaire for (Type& x : iterable) si *it ne renvoie pas de référence (mais s'il renvoie une référence, vous pouvez également utiliser la version de copie).

Notez également que operator++() définit la version prefix de l'opérateur ++ - toutefois, il sera également utilisé comme opérateur postfix, à moins que vous ne définissiez explicitement un postfix ++. Le rangeed-for ne sera pas compilé si vous fournissez uniquement un suffixe ++, qui peut être déclaré comme btw.can comme étant operator++(int) (argument fictif).


Exemple de travail minimal:

#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);
    }
}

sortie

0123456789

Vous pouvez utiliser la macro suivante pour simuler la plage (par exemple, pour les anciens compilateurs 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);
 }

(utilisez declspec ou passez IType si votre compilateur n’a même pas auto).

Sortie:

 1234500000

Comme vous pouvez le constater, continue et break fonctionneront grâce à sa construction complexe. Voir http://www.chiark.greenend.org.uk/~sgtatham/mp/ pour plus de piratage du préprocesseur C afin de créer des structures de contrôle personnalisées.

16
masterxilo

Cette syntaxe proposée par Intellisense n’est pas C++; ou c'est une extension MSVC. 

C++ 11 a basé sur les boucles for - basées sur la plage pour parcourir les éléments d'un conteneur. Vous devez implémenter pour votre classe les fonctions membres begin() et end() qui renverront les itérateurs au premier élément et à un passé après le dernier. Bien entendu, cela signifie que vous devez également mettre en œuvre des itérateurs adaptés à votre classe. Si vous voulez vraiment aller dans cette voie, vous voudrez peut-être consulter Boost.IteratorFacade ; cela vous évitera beaucoup de mettre en œuvre vous-même des itérateurs.

Après cela, vous pourrez écrire ceci: 

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

De plus, comme vous débutez dans le langage C++, je veux m'assurer que vous savez que la bibliothèque standard contient plusieurs classes conteneur .

7
Praetorian

C++ n'a pas la fonctionnalité de boucle for_each dans sa syntaxe. Vous devez utiliser c ++ 11 ou utiliser la fonction template 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

Comme @yngum le suggère, vous pouvez faire en sorte que l'extension VC++ for each fonctionne avec tout type de collection arbitraire en définissant les méthodes begin() et end() sur la collection pour renvoyer un itérateur personnalisé. Votre itérateur doit à son tour implémenter l'interface nécessaire (opérateur de déréférence, opérateur d'incrément, etc.). Je l'ai fait pour envelopper toutes les classes de collection MFC pour le code hérité. C'est un peu de travail, mais peut être fait.

0
Scott Jones