Schnittstelle für Iteration durch Member-Container
-
Hm. ich würd eine Schnittstelle anbieten, die ein Akkumulatorobjekt annimmt, da nacheinander die Elemente des containers reinstopft und dann den Akkumulator wieder zurückgibt. Der Akkumulator muss nur einen op() mit einem Element als argument haben. Beispiel:
struct Element { int i; }; class MyClass { std::vector<Element> myvec; public: template <class Acc> void allElems(Acc& acc) { BOOST_FOREACH(Element& e, myvec) { acc(e); } } }; struct MaxAcc { Element * pE; void operator(Element& e) { if (pE == 0 || e.i > pE->i) pE = &e; } MaxAcc() : pE (0) {} } struct ContainsZeroAcc { bool hasZero; ContainsZeroAcc() : hasZero(false) {} void operator(Element& e) { if (e.i == 0) hasZero = true; } }Ich denk du siehst worauf ich hinauswill.
-
was spricht gegen const_iterator? Dann hast du lesezugriff, aber niemand kann deine Elemente verändern.
-
pumuckl schrieb:
Hm. ich würd eine Schnittstelle anbieten, die ein Akkumulatorobjekt annimmt, da nacheinander die Elemente des containers reinstopft und dann den Akkumulator wieder zurückgibt.
Das wäre dann eine ausgebaute Version von Punkt 4), bei der man auch Daten nach aussen transportieren kann. Sieht schonmal sehr gut aus, vielen Dank für den Vorschlag!
otze schrieb:
was spricht gegen const_iterator? Dann hast du lesezugriff, aber niemand kann deine Elemente verändern.
Teilweise will ich ja die Elemente verändern können. Mir gefällt hier einfach dieser standardmässige Iteratoransatz nicht besonders. Klar, das Iterator-Konzept ist eigentlich genial, besonders in Zusammenarbeit mit generischen STL-Algorithmen und solchen Sachen. Trotzdem scheinen mir hier andere Verfahren geeigneter.
Noch eine allgemeine Frage: Habt ihr schon jemals sowas Ähnliches wie bei Punkt 2) und 3), besonders 2) verwendet? Die Vorgehensweise dort weist zwar einige Probleme auf, aber grundsätzlich finde ich die Idee sehr interessant, besonders weil die Anwendung wahnsinnig simpel ist.
-
Nexus schrieb:
Noch eine allgemeine Frage: Habt ihr schon jemals sowas Ähnliches wie bei Punkt 2) und 3), besonders 2) verwendet? Die Vorgehensweise dort weist zwar einige Probleme auf, aber grundsätzlich finde ich die Idee sehr interessant, besonders weil die Anwendung wahnsinnig simpel ist.
Von 2) würde ich in jedem Fall die Finger lassen, da du dadurch zwei Dinge vermischt: das eigentliche Objekt, und den Enumerations-Status. Letzterer hat da drin nämlich nix verloren. Man kann sich damit z.B. schnell ins Knie schiessen, wenn an mehreren Stellen enumeriert wird. Wenn FunktionA() enumeriert, mitten drin FunktionB() aufruft, die wieder enumertiert -> Chaos perfekt.
Was du als Wrapper-Iterator bezeichnest (3), würde ich Enumerator nennen. Siehe .NET Framework und Java Standard-Library. Grundsätzlich OK, wenn auch etwas unüblich in C++.
-
hustbaer schrieb:
Von 2) würde ich in jedem Fall die Finger lassen, da du dadurch zwei Dinge vermischt: das eigentliche Objekt, und den Enumerations-Status. Letzterer hat da drin nämlich nix verloren. Man kann sich damit z.B. schnell ins Knie schiessen, wenn an mehreren Stellen enumeriert wird. Wenn FunktionA() enumeriert, mitten drin FunktionB() aufruft, die wieder enumertiert -> Chaos perfekt.
Genau, das hab ich dort ja noch hingeschrieben. Hmm, es ist wohl tatsächlich klüger, wenn ich da nichts für eine etwas schönere Anwendung riskiere. Es gibt ja schliesslich immer noch Makros...

hustbaer schrieb:
Was du als Wrapper-Iterator bezeichnest (3), würde ich Enumerator nennen. Siehe .NET Framework und Java Standard-Library. Grundsätzlich OK, wenn auch etwas unüblich in C++.
Okay. Vielen Dank für die Anmerkungen!
-
Nexus schrieb:
hustbaer schrieb:
Von 2) würde ich in jedem Fall die Finger lassen, da du dadurch zwei Dinge vermischt: das eigentliche Objekt, und den Enumerations-Status. Letzterer hat da drin nämlich nix verloren. Man kann sich damit z.B. schnell ins Knie schiessen, wenn an mehreren Stellen enumeriert wird. Wenn FunktionA() enumeriert, mitten drin FunktionB() aufruft, die wieder enumertiert -> Chaos perfekt.
Genau, das hab ich dort ja noch hingeschrieben.
Oops, ja, hab ich überlesen.
Hmm, es ist wohl tatsächlich klüger, wenn ich da nichts für eine etwas schönere Anwendung riskiere. Es gibt ja schliesslich immer noch Makros...

AAAAH!

Wo willst du da Makros einsetzen? Und wieso?
-
Nexus schrieb:
Diese Schnittstelle muss ja nicht zwingend aus Iteratoren bestehen, nur weil das in C++ gängige Praxis ist. Zu den Abhängigkeiten: Es könnte ja mal sein, dass ich in Zukunft auf einen dynamischen Container als Member verzichte und die einzelnen Elemente jeweils neu berechne (aus welchem Grund auch immer). Mit einer Indirektion (Fälle 2-4) könnte ich die Schnittstelle trotz anderer Implementierung erhalten.
Iteratoren würden dir diese flexibilität ebenfalls lassen.
Es gibt auch keine Abhängigkeiten oder sonstwas.
Ich sehe immer noch keinen Grund gegen Iteratoren/Ranges.
-
hustbaer schrieb:
AAAAH!

Wo willst du da Makros einsetzen? Und wieso?Habe ich nicht wirklich vor, der Satz war nicht allzu ernst gemeint...

Shade Of Mine schrieb:
Iteratoren würden dir diese flexibilität ebenfalls lassen.
[...]
Ich sehe immer noch keinen Grund gegen Iteratoren/Ranges.Und ich sehe keinen Grund gegen einen alternativen Ansatz.

Wieso sollte man immer den 0815-Weg wählen? Iteratoren sind in C++ die Standardvorgehensweise fürs Iterieren, aber weshalb engstirnig darauf beharren, wenn bessere Alternativen offen stehen? Was mir an den Iteratoren nicht passt, habe ich ja bereits mehrfach erklärt. Wenn ich direkte Iteratoren in den Container habe, kann ich damit Dinge anstellen, die nicht gewollt sind. Eine Indirektion über Ranges scheint mir etwas kompliziert und vor allem unnötig, da ich einfachere Möglichkeiten sehe, die mir genauso gut oder sogar besser liefern, was ich will. Sind das schlechte Gründe? Falls bei Nicht-Iterator-Ansätzen nur die Flexibilität fehlt, ist das genau das, was ich erreichen möchte.Was spricht zum Beispiel gegen pumuckls Lösung? Sie ist wohl nicht der Weisheit letzter Schluss, aber für meinen Fall ziemlich ideal. Das einzige, was eventuell ein Nachteil sein könnte, ist eine Extra-Funktion zu schreiben. Aber besonders wenn man viele Aktionen durchführen möchte, bietet sich das sowieso an, und so tragisch ist das wirklich nicht.
Shade Of Mine schrieb:
Es gibt auch keine Abhängigkeiten oder sonstwas.
Mit direkten Iteratoren schon, ich habe ja oben einen solchen Fall beschrieben. Oder inwiefern bist du damit nicht einverstanden? Möglicherweise kann dieses Problem mit einer Range-Abstraktion umgangen werden, aber ich stelle mir das nicht besonders schön vor, wenn kein Container vorhanden ist. Da ich jetzt eine funktionierende und elegante Lösung habe, würde mich trotzdem noch interessieren, was an Iteratoren/Ranges von Vorteil wäre. Wie gesagt: Ich will lediglich für alle Elemente etwas tun, und alles Weitere möglichst vermeiden.
-
Da ich jetzt eine funktionierende und elegante Lösung habe, würde mich trotzdem noch interessieren, was an Iteratoren/Ranges von Vorteil wäre. Wie gesagt: Ich will lediglich für alle Elemente etwas tun, und alles Weitere möglichst vermeiden.
Der klare Vorteil von Iteratoren ist ja, dass so beliebige Algorithmen bauen kann, welche auf diesen Iteratoren arbeitet und somit sonst nix von der Klasse sonst wissen muss.
Wenn du das jetzt aber nicht willst/brauchst, oder lediglich ein Subset davon brauchst, dann müsste man es imo nicht wirklich mit Iteratoren machen.
-
Nexus schrieb:
Wenn ich direkte Iteratoren in den Container habe, kann ich damit Dinge anstellen, die nicht gewollt sind.
Welche zum Beispiel? Du musst ja nicht direkt den RandomAcess-Iterator des std::vector rauslassen sondern könntest stattdessen einen reinen Forward-Iterator anbieten.
-
drakon schrieb:
Wenn du das jetzt aber nicht willst/brauchst, oder lediglich ein Subset davon brauchst, dann müsste man es imo nicht wirklich mit Iteratoren machen.
Danke.

pumuckl schrieb:
Welche zum Beispiel? Du musst ja nicht direkt den RandomAcess-Iterator des std::vector rauslassen sondern könntest stattdessen einen reinen Forward-Iterator anbieten.
Dann müsste ich wieder einen Iterator wrappen, das geht dann in Richtung Ansatz 3), nur halt C++-Style. Du hast Recht, viel Schlimmes kann man damit nicht mehr anstellen, bzw. müsste man das fast mutwillig tun.
Hm... Ich muss mal eine Entscheidung treffen. Vorerst werde ich wahrscheinlich den Funktor-Ansatz verfolgen und schauen, ob er sich auf Dauer bewährt. Mit Iteratoren habe ich schon oft experimentiert, deshalb wäre es auch interessant, mal was Neues auszuprobieren. Es muss sowieso einiges an Code angepasst werden. Später kann ich das immer noch überdenken, sollten sich gewisse Schwierigkeiten erst dort zeigen. Nochmals danke an alle für die Beiträge!

-
Ich stand zufaellig letztens vor EXAKT der selben Frage und habe mir die gleichen Gedanken gemacht. Ich hatte quick'n'dirty die von Dir beschriebene Version 5 gewaehlt. Bin jetzt auf Version 1 umgeschwenkt.
Am saubersten/besten/<setze passendes Adjektiv hier ein> (eben auch unter Deinen Gesichtspunkten) erscheint mir Version 4. Ich stellte nur fest, dass die sich nicht wirklich anwenden liess (in meinem Fall), weil nicht einfach nur eine Funktion auf jedes Element angewendet werden sollte, sondern auch noch ein State enthalten war. Schwups war man am Funktor schreiben und das ganze artete extrem aus, weil man dem Funktor eine ganze Menge Sachen (die eigentlich in der zugreifenden Klasse vorhanden waren) mitgeben musste. Das wiederrum ging nur sehr eingeschraenkt, weil die Sachen von aussen garnicht zugaenglich waren usw. usf.
Bin mir nicht sicher, was ich damit sagen will, vllt einfach: mach Dir nicht garsoviele Gedanken, wie Du diese eine Schnittstelle wirklich nur auf diesen einen Anwendungsfall beschraenkst; mit der naechsten Aenderung schmeisst du vllt wieder alles uebern Haufen :oDer (Design-) Perfektionist

-
Das hat was. In der Praxis wird sich nicht alles wie in der Theorie abspielen... Naja. Irgendwie muss ich mal was ausprobieren, nachher ist man immer schlauer. Aber wenigstens habe ich so mehrere Möglichkeiten ausprobiert.

Aber interessant, dass dich das auch erst gerade beschäftigt hat. Auf jeden Fall merci für den Hinweis!
-
Nochwas: wenns nur ums Lesen geht, dann kann man noch eine "Get" Funktion machen, die einen Output-Iterator als Parameter nimmt:
struct Element { int i; }; class MyClass { std::vector<Element> myvec; public: template <class OutputIterator> void GetElements(OutputIterator it) { BOOST_FOREACH(Element const& e, myvec) { *it = e; ++it; } } }; struct my_functor { void operator()(Element const& e) const { // do somethine with e } }; void test() { MyClass mc; // in vector kopieren std::vector<Element> eles; mc.GetElements(std::back_inserter(eles)); // direkt ausgeben mc.GetElements(std::ostream_iterator<Element>(std::cout,", ")); // in funktor umleiten mc.GetElements(boost::make_function_output_iterator(my_functor())); }Das wäre
* sicher (keiner kann was machen was du nicht willst)
* halbwegs bekannt/nicht unüblich
* sehr flexibel
* IMO halbwegs "schön" (ansichtssache, klar)
* "STL konform"
-
Sehr gut, noch ein Vorschlag, mit dem ich etwas herumtüfteln kann. Es gibt doch viel mehr Möglichkeiten als ich eigentlich angenommen habe, aber so wird mir wenigstens nicht langweilig. Danke!
