48 votes

Existe-t-il un équivalent standard C++ de IEnumerable<T> en C# ?

Ou est-il sûr d'utiliser le vecteur si l'énumérateur de T ne fait que lister tous les éléments ?

56voto

Ben Voigt Points 151460

Il n'est pas nécessaire en C++, et voici pourquoi :

C# ne supporte que le polymorphisme dynamique. Ainsi, pour créer un algorithme réutilisable, vous avez besoin d'une interface que tous les itérateurs mettront en œuvre. Il s'agit de IEnumerator<T> et IEnumerable<T> est une usine pour retourner un itérateur.

Les modèles C++, en revanche, prennent en charge le typage des canards. Cela signifie que vous n'avez pas besoin de contraindre un paramètre de type générique par une interface afin d'accéder aux membres -- le compilateur recherchera les membres par leur nom pour chaque instanciation individuelle du modèle.

Les conteneurs et itérateurs C++ ont des interfaces implicites, ce qui est équivalent à .NET IEnumerable<T> , IEnumerator<T> , ICollection<T> , IList<T> à savoir :

Pour les conteneurs :

  • iterator y const_iterator typedefs
  • begin() fonction de membre - répond au besoin de IEnumerable<T>::GetEnumerator()
  • end() fonction membre -- au lieu de IEnumerator<T>::MoveNext() valeur de retour

Pour les itérateurs avant :

  • value_type typedef
  • operator++ -- au lieu de IEnumerator<T>::MoveNext()
  • operator* y operator-> -- au lieu de IEnumerator<T>::Current
  • type de retour de référence de operator* -- au lieu de IList<T> indexeur setter
  • operator== y operator!= -- Il n'y a pas d'équivalent réel en .NET, mais avec le conteneur end() correspond à IEnumerator<T>::MoveNext() valeur de retour

Pour les itérateurs à accès aléatoire :

  • operator+ , operator- , operator[] -- au lieu de IList<T>

Si vous les définissez, les algorithmes standard fonctionneront avec votre conteneur et votre itérateur. Aucune interface n'est nécessaire, aucune fonction virtuelle n'est nécessaire. Le fait de ne pas utiliser de fonctions virtuelles rend le code générique C++ plus rapide que le code .NET équivalent, parfois beaucoup plus rapide.


Remarque : lors de l'écriture d'algorithmes génériques, il est préférable d'utiliser la fonction std::begin(container) y std::end(container) au lieu des fonctions membres du conteneur. Cela permet à votre algorithme d'être utilisé avec des tableaux bruts (qui n'ont pas de fonctions membres) en plus des conteneurs STL. Les tableaux bruts et les pointeurs bruts répondent à toutes les autres exigences des conteneurs et des itérateurs, à cette seule exception près.

12voto

J.P. Points 314

Si nous nous en tenons strictement à la question, la réponse est non, pour autant que je sache. Les gens n'ont pas cessé de répondre en indiquant quel est le substitut disponible en C++, ce qui peut être une bonne information mais pas une réponse, et ce que le PO savait très probablement déjà.

Je ne suis pas du tout d'accord pour dire que "ce n'est pas nécessaire", c'est juste que la conception des bibliothèques standard C++ et .NET est différente. La principale caractéristique de IEnumerable<> est qu'il est polymorphe, et qu'il permet donc à l'appelant d'utiliser la classe qu'il veut (array, List, Set etc.), tout en assurant un typage fort à la compilation, sans risque même dans les API des bibliothèques.

La seule alternative en C++ est les templates. Mais les modèles C++ ne sont pas des génériques d'exécution typés de manière sûre, ce sont en fait des sortes de macros. Donc, tout d'abord, avec les modèles en C++, vous êtes obligé de fournir le code source complet du modèle à quiconque a besoin d'utiliser votre modèle. De plus, si vous faites de l'API de votre bibliothèque un modèle, vous perdez la possibilité de garantir qu'un appel à celui-ci sera compilé, et le code n'est pas automatiquement auto-documenté.

Je compatis pleinement avec tout autre programmeur qui utilise à la fois C# et C++ et qui est frustré par ce point.

Cependant, il est prévu que C++2X ajoute des fonctionnalités telles que les intervalles (ce qui pourrait satisfaire le PO ?); ainsi que des concepts (qui traitent de la vérification de type faible/mauvaise des modèles -- faille admis par Bjarne Stroustrup lui-même), et les modules (qui peuvent ou non aider à réduire la douleur des modèles d'en-tête seulement).

9voto

FredOverflow Points 88201

La méthode standard du C++ consiste à passer deux itérateurs :

template<typename ForwardIterator>
void some_function(ForwardIterator begin, ForwardIterator end)
{
    for (; begin != end; ++begin)
    {
        do_something_with(*begin);
    }
}

Exemple de code client :

std::vector<int> vec = {2, 3, 5, 7, 11, 13, 17, 19};
some_function(vec.begin(), vec.end());

std::list<int> lst = {2, 3, 5, 7, 11, 13, 17, 19};
some_function(lst.begin(), lst.end());

int arr[] = {2, 3, 5, 7, 11, 13, 17, 19};
some_function(arr + 0, arr + 8);

Yay la programmation générique !

6voto

Andrew Shepherd Points 16670

IEnumerable<T> est conceptuellement très différent de vector .

El IEnumerable fournit à l'avance , en lecture seule l'accès à une séquence d'objets, quel que soit le conteneur (le cas échéant) qui contient les objets. A vector est en fait un conteneur lui-même.

En C++, si vous voulez donner accès à un conteneur sans donner les détails de ce conteneur, la convention est de passer dans deux itérateurs représentant le début et la fin du conteneur.

Un bon exemple est la définition de la STL C++ de la fonction accumuler que l'on peut opposer à IEnumerable<T>. Agrégat

En C++

   int GetProduct(const vector<int>& v)
   {
         // We don't provide the container, but two iterators
         return std::accumulate(v.begin(), v.end(), 1, multiplies<int>());
   }

En C#

  int GetProduct(IEnumerable<int> v)
  {
        v.Aggregate(1, (l, r) => l*r);
  }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X