8
votes

Justification de la conception des itérateurs C # (comparaison de C ++)

J'ai trouvé un sujet similaire:

itérateurs en C ++ (stl) vs java, est Il y a une différence conceptuelle?

qui traite essentiellement de Java Itérateur (qui est similaire à C #) étant incapable de monter en arrière. p>

donc je voudrais me concentrer sur les limites - C ++ Itérateur ne connaît pas sa limite, vous avez par vous-même comparer l'itérateur donné avec la limite. En C # Itérateur en sait plus - vous pouvez le dire sans comparer à aucune référence externe, si l'itérateur est valide ou non. P>

i préférez C ++ moyen, car une fois que vous avez un itérateur, vous pouvez définir tout itérateur comme une limite. En d'autres termes si vous souhaitez obtenir seulement quelques éléments à la place de la collection complète, vous n'avez pas à modifier l'itérateur (en C ++). Pour moi, il est plus "pur" (clair). P>

mais bien sûr que MS connaissait ceci et C ++ tout en concevant C #. Alors, quels sont les avantages de la manière C #? Quelle approche est plus puissante (ce qui conduit à des fonctions plus élégantes basées sur des itérateurs). Qu'est-ce que je manque? P>

Si vous avez des pensées sur c # vs c ++ itérateurs conception forts> autres que leurs limites (limites), veuillez également répondre. P>

Note Strong>: (Juste au cas où) s'il vous plaît, gardez la discussion strictement technique. No C ++ / C # Flamewar. P>

Edits h1>

Comme Tzaman l'a dit "Il n'y a aucun avantage à avoir la limite d'être exprimée séparément, car il n'y a aucun moyen d'y arriver, sauf de marcher à l'exception d'un élément à un autre temps." Cependant, il n'est pas si difficile de construire un itérateur C # qui fait plusieurs étapes un temps, donc la question - est-il un avantage d'avoir une limite explicite Itérateur (comme en C ++)? Si oui - quoi? P>

@jon, H2>

Edit1: EM> Disons que vous avez une fonction FOO qui fait quelque chose sur les itérateurs (l'exemple est très naïf !) p> xxx pré>

et maintenant, vous souhaitez appeler la barre de fonction sur tous les éléments, à l'exception des 10 derniers. p> xxx pré>

in c # (Si je ne me trompe pas), vous devriez fournir une méthode supplémentaire à cet itérateur de changer sa limite em>, quelque chose comme ceci: p>

sort(IEnumerator<T> iter)


6 commentaires

@macias, comme n'étant ni un développeur C ++ ou C #, je préfère le style C # dans votre exemple. Cela me semble plus naturel.


Comment les séquences infinies sont-elles traitées dans C ++? Qu'est-ce que iter_end serait dans de telles circonstances?


@Damien, je n'ai jamais travaillé avec une séquence infinitive en C ++.


Deux mots: principe de responsabilité.


@Damien: Pour les séquences infinies, iter_end doit simplement être une valeur spéciale qui ne sera jamais atteinte (par exemple, un iStream_iterator peut désigner une plage infinie si le flux ne finit jamais). Toutefois, si un algorithme est conçu pour fonctionner avec une séquence infinie, il ne devrait probablement prendre qu'un seul itérateur. Par exemple, l'algorithme std :: copie prend deux itérateurs d'entrée indiquant la plage source (ou la séquence), mais un seul itérateur de sortie, qui peut être plus ou moins vu comme une plage infinie où la copie sera être fait. [...]


[...] Les itérateurs qui génèrent des valeurs peuvent également être considérés comme des itérateurs sur une séquence infinie. Je pense que c'est à Boost, ils appellent cela une "séquence paresseuse" (voir par exemple boost :: comptage_iterator, qui génère des valeurs incrémentielles).


5 Réponses :


7
votes

Il me semble que la conception C # est plus encapsulée: une séquence s'épuise ou fait, indépendante de toute autre chose. Où cela fait-il sens em> pour comparer une limite avec une autre?

Si vous ne voulez que prendre quelques éléments, alors LINQ fournit un nombre de façons de construire une séquence d'une autre, par exemple P >

Bar(list.Take(list.Count - 10))


7 commentaires

Ouais ... Cela semble certainement plus gentil, mais qu'est-ce qu'ils nous attendaient à faire avant Linq?


Merci pour votre réponse. Dans votre exemple et ci-dessous, vous avez supposé que votre "entrée" est une collection. Mais si c'est déjà un itérateur? Étant donné que les commentaires ne forment pas bien, je modifie mon message.


@Mark: en C # 1, il aurait été assez moche de le faire. En C # 2, vous pouvez construire votre propre pseudo-linq raisonnablement facilement à l'aide de blocs d'itérateurs - il manquerait cependant la concision des expressions et des méthodes d'extension Lambda.


@Jon, je sais qu'il ne s'agit pas de modifier les éléments de collecte :-) Maintenant, à propos de l'exemple - vous ne pouvez pas compter sur le fait que vous recevez gratuitement, par exemple pour la liste liée, c'est O (n) pour l'obtenir, donc C ++ dans ce cas serait beaucoup plus rapide. Et en plus, vous avez encore une fois utilisé Collection au lieu de Itérateur . Donc, je suis de plus en plus curieux - si vraiment C ++ Itérateurs sont des collections C # Collections (non itérateurs) contrepartie? Ou peut-être que je repasse ma question - C ++ STL Fonctions générales (#include ) Travailler sur des itérateurs, C # stl fonctionnerait sur Collections CORRECT?


@Macias: vous obtenez le compte pour O (1) dans un .net LinkedList ... et vous supposez également que vous pouvez facilement accéder à "10 à partir de la fin" dans une liste liée , qui nécessitera qu'il s'agisse d'une liste doublement liée. Mais oui, je vous reconnaîtrai que dans le cas d'une liste doublement liée qui ne conserve pas de compte, tout sauf que les éléments X final sont plus difficiles à .NET. Je ne peux pas dire que j'ai rencontré cette situation exacte cependant :) En termes de collections vs itérateurs: il serait plus précis de dire que .NET API fonctionne souvent en termes de séquences plutôt que des collections . Toutes les séquences ne sont pas une collection normale.


Une expression comme liste.end () - n est O (n) dans une liste STD :: Liste également. Et O (1) pour un STD :: Vecteur, juste comme une liste <>. Aucune différence là-bas.


@Jon, donc, c # stl homologue serait basé sur des séquences et non des itérateurs. Ok, c'est un quart de travail pour ma pensée :-) merci. @Hans, bon point. Les étapes exactes diffèrent cependant.



3
votes

Alors, quels sont les avantages de la manière C #? p>

encapsulation pour un. Il rend plus difficile de gâcher vos limites d'itération si vous ne définissez pas la séquence d'itération manuellement. P>

C ++ Exemple: P>

std::vector<int> a;
std::vector<int> b;
std::vector<int>::iterator ba = a.begin();
std::vector<int>::iterator ea = a.end();
std::vector<int>::iterator bb = b.begin();
std::vector<int>::iterator eb = b.end();
// lots of code here
for(auto itr = ba; itr != eb; itr++) // iterator tries to "cross" vectors
                                     // you get undefined behavior, depending 
                                     // on what you do with itr

3 commentaires

Votre exemple est très convaincant, merci pour ça! Puissant = Vous pouvez exprimer la plupart des algorithmes (trier, partition, trouver, tout, tout etc.) de manière claire.


@MACIAS: Vous pouvez exprimer une partition, trouver, toutes les séquences C # (et incroyablement facilement, en utilisant des expressions Lambda).


Pour cette définition, les itérateurs C ++ sont définitivement plus puissants. Vous avez des itérateurs d'E / S, spécifiant des gammes personnalisées pour algorithmes (`std :: tr tri (c.begin (), c.begin (), c.begin () + 3)`), utilisez les mêmes algorithmes avec des itérateurs et des point-points cru ) Et ainsi de suite.



6
votes

Je pense aussi longtemps que vous ne parlez que sur avance itérateurs, j'aime la solution C # - la séquence connaît sa propre limite, vous pouvez le transformer facilement via Linq, etc. Il n'y a aucun avantage à avoir la limite d'être exprimée séparément, car il n'y a aucun moyen de y arriver sauf marcher un élément à la fois.

Cependant, les itérateurs C ++ sont beaucoup plus puissants - vous pouvez avoir des itérateurs bidirectionnels, ou des itérateurs aléatoires, qui vous permettent de faire des choses qui ne sont pas possibles avec un itérateur séquentiel à sens unique. Un itérateur séquentiel est dans un sens une généralisation d'une liste liée , tandis qu'un itérateur d'accès aléatoire est une généralisation d'un tableau (em>. C'est pourquoi vous pouvez exprimer efficacement des algorithmes comme Quicksort en termes d'itérateurs C ++, tandis que cela serait impossible avec un ienumerable .


8 commentaires

Oh, j'aime votre réponse - cependant pourriez-vous s'il vous plaît expliquer un peu la dernière déclaration. Ou postez un lien vers un ext. référence. À propos des différents types d'itérateurs - on ne nie pas l'autre, c'est-à-dire que vous pourriez avoir c # itérateur (avec limite), mais bidirectionnel.


Pour "Itérateur d'accès aléatoire", vous utilisez simplement ilist bien sûr. La raison de mon +1 ici était l'idée d'un itérateur bidirectionnel, lequel .Net ne supporte pas. Je ne peux pas dire que c'est quelque chose que je voudrais terriblement souvent, mais c'est définitivement une omission intéressante.


@Jon - bien sûr; Toujours un ilist est (dans les termes de macias) une représentation de la Collection elle-même plutôt qu'une position dans cette collection. Je suppose que le concept de C ++ Itérateur était le meilleur moyen d'exposer une interface générique à une collection, sans avoir d'interfaces réelles dans la langue, sans aucun doute mieux que de forcer un ABC hériter simplement de pouvoir utiliser l'algorithme STL / Etc. Toute supposition de pourquoi il n'y a pas de fichier deque / ibidirectionnel ? Ils sont utiles ...


@tzman: Dans de nombreux cas, vous pouvez simplement utiliser un linkedlist en tant que deque bien que cela vienne avec un coût de mémoire. Je ne sais pas pourquoi il n'y a pas de mise en œuvre de tampon circulaire, autre que celle de la mise en œuvre tout coûte temps ... je soupçonne que la valeur n'a pas été considérée assez bien.


Y a-t-il une raison pour que ienumerator ne devrait pas prendre en charge un int bouge (int); méthode qui, pour une valeur non négative, essayerait d'appeler MOVENNEXT Le nombre de fois spécifié et renvoie 0 s'il pourrait déplacer le montant total, ou la quantité de déficit si elle ne pouvait pas (la dernière quantité étant utile si plusieurs ienumerables Les collections sont concaténées)? Tout ienumerator peut mettre en œuvre cette méthode pour appeler movenext () si rien d'autre, mais de nombreuses implémentations pourraient utiliser des approches alternatives d'approches de grandeur plus rapidement que les appels répétés vers MOVENEXT .


@supercat Il y a, c'est ce qu'on appelle saute . Je suis à peu près sûr qu'il est spécialisé d'être O (1) pour la liste sous-jacente Types, mais je n'ai pas vérifié.


@Tzaman: la méthode d'extension Ignorer , comme Nombre , est spécialisé pour être O (1) pour quelques cas spéciaux, mais ces spécialisations échouent totalement si par exemple. On utilise la méthode d'extension concat pour joindre un itérateur à dix éléments à la fin d'une liste d'articles. Par contraste, si ienumerator avait une méthode (int) , et on a cherché à déplacer 1 000 005 articles à travers l'énumérateur produit par la concaténation susmentionnée, elle pourrait alors demander au Liste .enumerator Pour déplacer 1 000 005 éléments via la liste , devez-la signaler qu'il est tombé cinq courts, puis déplacez cinq éléments à travers l'autre itérateur.


@Tzaman: une telle approche ne s'appuierait pas sur des spécialisations spécifiques à un type et serait en mesure de traiter efficacement les agrégations composites comme celles produites par concat - quelque chose que les spécialisations ne seraient pas en mesure de gérer parce qu'ils ne peuvent pas voir à l'intérieur de ces agrégations.



3
votes

Vous comparez des pommes et des oranges. Les classes de collection STL avaient des objectifs de conception complètement différents. Ce sont une conception unique des collections, vous ne pouvez jamais recevoir les classes fournies. Il n'y a aucun moyen de personnaliser une collection, les classes STL n'ont pas été conçues pour être héritées de.

Très différent des .NET, leur noyau est l'ICOLLECTION, les interfaces ILLITION ET ILIST. Le .NET Framework contient des centaines de classes de collecte, personnalisées pour obtenir leur travail spécifique.

qui affectait également les conceptions de leurs itérateurs. Les itérateurs STL doivent fournir beaucoup de flexibilité puisque les collections qu'elles itérations ne peuvent pas être personnalisées. Il y a un coût inhérent associé à cela. Il existe 5 types d'itérateurs STL différents, toutes les classes de collecte ne prennent pas en charge les 5 types différents. La perte de généralité n'est jamais une bonne chose dans une conception de la bibliothèque.

Un argument peut être fait que les itérateurs C ++ sont plus flexibles, je vais faire l'affirmation opposée. La simplicité d'Ienumerator était un catalyseur pour de très précieuses fonctionnalités .NET. Le mot clé C # rendement par exemple, il dissocie complètement une collection avec le comportement d'un itérateur. LINQ n'aurait pas eu lieu si ce n'était pas pour un simple type d'itérateur. Le soutien de la covariance n'aurait pas été possible.

En ce qui concerne votre edit2 , non, les itérateurs .NET sont des cours distincts, tout comme les C ++. Assurez-vous de comprendre la différence entre iEnumerable (implémenté par la collection) et ienumerator (implémenté par l'itérateur).


3 commentaires

Ok, beaucoup ira à éditer :-) Je pense que vous me faites convaincre d'un exemple de rendement. C'est à dire. Avec deux itérateurs pour définir les limites que vous devez savoir à l'avance la limite (la limite peut changer, mais elle doit être là). Donc, la contrepartie de rendement devrait transférer deux valeurs à chaque fois, ce qui signifie que l'itérateur actuel devrait connaître sa limite -> qui nous conduit à C # Itérateur. Joli.


Je pose respectueusement que c'est une mauvaise réponse. Premièrement, vous êtes factuellement incorrect sur les collections et les itérateurs STL: bien sûr, ils peuvent être hérités et personnalisés. La STL est conçue pour séparer les algorithmes de la structure - c'est-à-dire le std :: copier ou std :: for_ach Les fonctions ne doivent pas avoir à se soucier de ses arguments pointant vers, seulement qu'ils sont des itérateurs valides. Vous avez également tort de C #: il n'y a pas d'itérateurs, seulement des collections.


Bon, d'accord. Je devrais clarifier: les interfaces iEnumérables et iénumérables exposent une méthode appelée getenumerator qui renvoie un objet implémentant ienumerator , qui expose une méthode appelée movenext . Cependant, cet énumérateur n'est pas vraiment la même chose qu'un itérateur.



0
votes

J'ai trouvé ce sujet à cause du problème suivant. Il y a une séquence qui doit être avancée d'un élément à la fois. Cependant, à certains points, un grand nombre d'éléments doivent être ignorés. Lorsque de tels sauts se produisent, il est inefficace de faire tout ce que sauter et redémarrer à la nouvelle index (c'est-à-dire qu'il ne s'agit pas d'exécuter la boucle pour les indices sautés, même sans une exécution de corps en boucle.)

Voici un extrait de C # à donner vous l'idée, sauf que c'est à l'aide d'un index INT plutôt que d'un énumérateur. xxx

Le point est que cela se passe à l'élément suivant peut être optimisé pour cette séquence particulière, mais Sauter des éléments (et un accès aléatoire complet) est plus coûteux. @Jon, en utilisant IList est donc inefficace à cette fin. Le rendement ne permet pas directement à ce style.

J'ai essayé de différentes façons de le faire en C #, y compris à l'aide d'un style Lambda Foreach () pour le bloc interne, qui renvoie l'index suivant. Cependant, cela ne fonctionne pas très bien.

L'approche résultera probablement du moins de frais généraux est un type d'itérateur simple - non basé sur iEnumérable ou donné - cela me permet d'imiter le code ci-dessus.

pensées?


0 commentaires