Ich habe einen Beispiel-Python-Code, den ich in C ++ nachahmen muss. Ich benötige keine spezifische Lösung (z. B. auf Co-Routine basierende Ertragslösungen, obwohl dies auch akzeptable Antworten wären). Ich muss lediglich die Semantik auf irgendeine Weise reproduzieren.
Python
Dies ist ein grundlegender Sequenzgenerator, der eindeutig zu groß ist, um eine materialisierte Version zu speichern.
def pair_sequence():
for i in range(2**32):
for j in range(2**32):
yield (i, j)
Das Ziel besteht darin, zwei Instanzen der obigen Sequenz beizubehalten und sie im Semi-Lockstep, jedoch in Blöcken, zu durchlaufen. Im folgenden Beispiel first_pass
verwendet der die Sequenz von Paaren, um den Puffer zu initialisieren, und der second_pass
regeneriert dieselbe exakte Sequenz und verarbeitet den Puffer erneut.
def run():
seq1 = pair_sequence()
seq2 = pair_sequence()
buffer = [0] * 1000
first_pass(seq1, buffer)
second_pass(seq2, buffer)
... repeat ...
C ++
Das einzige, was ich für eine Lösung in C ++ finden kann, ist die Nachahmung yield
mit C ++ - Coroutinen, aber ich habe keine gute Referenz dafür gefunden. Ich interessiere mich auch für alternative (nicht allgemeine) Lösungen für dieses Problem. Ich habe nicht genügend Speicherbudget, um eine Kopie der Sequenz zwischen den Durchläufen aufzubewahren.
Antworten:
Generatoren existieren in C ++ unter einem anderen Namen: Input Iterators . Zum Beispiel
std::cin
ähnelt das Lesen von einem Generator vonchar
.Sie müssen nur verstehen, was ein Generator tut:
In Ihrem trivialen Beispiel ist es einfach genug. Konzeptionell:
Natürlich wickeln wir dies als eine richtige Klasse ein:
Also hum yeah ... könnte sein, dass C ++ ein bisschen ausführlicher ist :)
quelle
In C ++ gibt es Iteratoren, aber die Implementierung eines Iterators ist nicht einfach: Man muss die Iteratorkonzepte konsultieren und die neue Iteratorklasse sorgfältig entwerfen, um sie zu implementieren. Zum Glück verfügt Boost über eine iterator_facade- Vorlage, die bei der Implementierung der Iteratoren und iterator-kompatiblen Generatoren helfen soll.
Manchmal kann eine stapellose Coroutine verwendet werden, um einen Iterator zu implementieren .
PS Siehe auch diesen Artikel, in dem sowohl ein
switch
Hack von Christopher M. Kohlhoff als auch Boost.Coroutine von Oliver Kowalke erwähnt werden. Oliver Kowalkes Arbeit ist eine Fortsetzung von Boost.Coroutine von Giovanni P. Deretta.PS Ich denke, Sie können auch eine Art Generator mit Lambdas schreiben :
Oder mit einem Funktor:
PS Hier ist ein Generator, der mit den Mordor- Coroutinen implementiert wurde :
quelle
Da Boost.Coroutine2 es jetzt sehr gut unterstützt (ich habe es gefunden, weil ich genau das gleiche
yield
Problem lösen wollte ), veröffentliche ich den C ++ - Code, der Ihrer ursprünglichen Absicht entspricht:In diesem Beispiel werden
pair_sequence
keine zusätzlichen Argumente verwendet. Wenn dies erforderlich iststd::bind
oder ein Lambda verwendet werden sollte, um ein Funktionsobjekt zu generieren, das nur ein Argument (vonpush_type
) akzeptiert , wenn es an dencoro_t::pull_type
Konstruktor übergeben wird.quelle
Alle Antworten, bei denen Sie einen eigenen Iterator schreiben, sind völlig falsch. Solche Antworten verfehlen völlig den Sinn der Python-Generatoren (eines der größten und einzigartigsten Merkmale der Sprache). Das Wichtigste an Generatoren ist, dass die Ausführung dort fortgesetzt wird, wo sie aufgehört hat. Dies passiert Iteratoren nicht. Stattdessen müssen Sie Statusinformationen manuell speichern, sodass beim erneuten Aufruf von Operator ++ oder Operator * die richtigen Informationen zu Beginn des nächsten Funktionsaufrufs vorhanden sind. Aus diesem Grund ist das Schreiben eines eigenen C ++ - Iterators ein gigantischer Schmerz. Generatoren sind elegant und leicht zu lesen und zu schreiben.
Ich glaube nicht, dass es ein gutes Analogon für Python-Generatoren in nativem C ++ gibt, zumindest noch nicht (es gibt ein Gerücht, dass die Ausbeute in C ++ 17 landen wird ). Sie können etwas Ähnliches erreichen, indem Sie auf Dritte zurückgreifen (z. B. Yongweis Boost-Vorschlag) oder Ihre eigenen rollen.
Ich würde sagen, das Nächste in nativem C ++ sind Threads. Ein Thread kann einen angehaltenen Satz lokaler Variablen verwalten und die Ausführung dort fortsetzen, wo er aufgehört hat, ähnlich wie bei Generatoren. Sie müssen jedoch ein wenig zusätzliche Infrastruktur bereitstellen, um die Kommunikation zwischen dem Generatorobjekt und seinem Aufrufer zu unterstützen. Z.B
Diese Lösung hat jedoch mehrere Nachteile:
quelle
Sie sollten wahrscheinlich Generatoren in std :: experimentell in Visual Studio 2015 überprüfen, z. B.: Https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/
Ich denke, es ist genau das, wonach Sie suchen. Gesamtgeneratoren sollten in C ++ 17 verfügbar sein, da dies nur eine experimentelle Microsoft VC-Funktion ist.
quelle
Wenn Sie dies nur für eine relativ kleine Anzahl spezifischer Generatoren tun müssen, können Sie jeden als Klasse implementieren, wobei die Mitgliedsdaten den lokalen Variablen der Python-Generatorfunktion entsprechen. Dann haben Sie eine nächste Funktion, die das nächste Ergebnis des Generators zurückgibt und dabei den internen Status aktualisiert.
Dies ähnelt im Grunde der Implementierung von Python-Generatoren, glaube ich. Der Hauptunterschied besteht darin, dass sie sich einen Teil des Bytecodes für die Generatorfunktion als Teil des "internen Zustands" merken können, was bedeutet, dass die Generatoren als Schleifen geschrieben werden können, die Ausbeuten enthalten. Sie müssten stattdessen den nächsten Wert aus dem vorherigen berechnen. Bei Ihnen
pair_sequence
ist das ziemlich trivial. Es ist möglicherweise nicht für komplexe Generatoren.Sie benötigen auch eine Möglichkeit, die Beendigung anzuzeigen. Wenn das, was Sie zurückgeben, "zeigerartig" ist und NULL kein gültiger Ertragswert sein sollte, können Sie einen NULL-Zeiger als Beendigungsindikator verwenden. Andernfalls benötigen Sie ein Außerbandsignal.
quelle
So etwas ist sehr ähnlich:
Die Verwendung des Operators () ist nur eine Frage dessen, was Sie mit diesem Generator tun möchten. Sie können ihn auch als Stream erstellen und sicherstellen, dass er sich beispielsweise an einen istream_iterator anpasst.
quelle
Verwenden von range-v3 :
quelle
So etwas wie das :
Anwendungsbeispiel:
Druckt die Zahlen von 0 bis 99
quelle
Nun, heute suchte ich auch nach einer einfachen Implementierung der Sammlung unter C ++ 11. Eigentlich war ich enttäuscht, weil alles, was ich gefunden habe, zu weit von Dingen wie Python-Generatoren oder C # -Ertragsoperatoren entfernt ist ... oder zu kompliziert.
Der Zweck besteht darin, eine Sammlung zu erstellen, die ihre Gegenstände nur dann ausgibt, wenn dies erforderlich ist.
Ich wollte, dass es so ist:
Ich fand diesen Beitrag, IMHO beste Antwort war über boost.coroutine2, von Yongwei Wu . Da es dem Autor am nächsten kommt.
Es lohnt sich, Boost Couroutinen zu lernen. Und ich werde es vielleicht am Wochenende tun. Bisher verwende ich jedoch meine sehr kleine Implementierung. Hoffe es hilft jemand anderem.
Unten finden Sie ein Beispiel für die Verwendung und anschließende Implementierung.
Example.cpp
Generator.h
quelle
Diese Antwort funktioniert in C (und daher denke ich, funktioniert auch in C ++)
Dies ist eine einfache, nicht objektorientierte Methode, um einen Generator nachzuahmen. Das hat bei mir wie erwartet funktioniert.
quelle
So wie eine Funktion das Konzept eines Stapels simuliert, simulieren Generatoren das Konzept einer Warteschlange. Der Rest ist Semantik.
Nebenbei bemerkt, Sie können eine Warteschlange immer mit einem Stapel simulieren, indem Sie einen Stapel von Operationen anstelle von Daten verwenden. Praktisch bedeutet dies, dass Sie ein warteschlangenähnliches Verhalten implementieren können, indem Sie ein Paar zurückgeben, dessen zweiter Wert entweder die nächste aufzurufende Funktion hat oder anzeigt, dass wir keine Werte mehr haben. Dies ist jedoch allgemeiner als das, was Rendite gegen Rendite bewirkt. Es ermöglicht die Simulation einer Warteschlange mit beliebigen Werten anstelle von homogenen Werten, die Sie von einem Generator erwarten, ohne jedoch eine vollständige interne Warteschlange beizubehalten.
Da C ++ keine natürliche Abstraktion für eine Warteschlange hat, müssen Sie Konstrukte verwenden, die eine Warteschlange intern implementieren. Die Antwort, die das Beispiel mit Iteratoren gab, ist also eine anständige Umsetzung des Konzepts.
Praktisch bedeutet dies, dass Sie etwas mit Bare-Bones-Warteschlangenfunktion implementieren können, wenn Sie nur etwas schnelles möchten und dann die Werte der Warteschlange genauso verbrauchen, wie Sie die Werte eines Generators verbrauchen würden.
quelle