Ein Iterator zur Außenwelt
Manchmal wollen wir gern Dinge mit der Standardbibliothek verbinden, die so erstmal nicht vorgesehen sind. Zu dem Zweck kann
ein selbst implementierter Iterator hilfreich sein. Im Beispiel hier soll ein OutputIterator
die Werte, die in ihn
geschrieben werden, in eine grafische Oberfläche übertragen. Dafür muss er natürlich die Funktionalität eines
OutputIterator
unterstützen und zusätzlich noch seine Fähigkeiten der Standardbibliothek bekanntgemacht werden.
Video
Code
|
|
Erklärung
Eine Menge Quelltext, der aber in verhältnismäßig übersichtliche Teile zerfällt. Die Zeilen 77 bis 86 und alle Qt-Header können wir eigentlich getrost ignorieren. Die sind bloß notwendig, um die grafische Oberfläche zu erzeugen (daher ist das direkte Kompilieren des Quellcodes heute auch nicht so einfach. Die Qt-Library muss nämlich noch angebunden werden.). Interessant ist lediglich, dass es in der Oberfläche ein QLabel gibt, welches Text anzeigen kann und in welches unser Iterator schreiben soll.
Herzstück des Programms ist das Template GuiIterator
. Als Templateparamater erhält es den Datentyp, den das entsprechende
Iteratorobjekt schreiben soll. Ein OutputIterator
muss nur relativ wenige Funktionen unterstützen: er muss inkrementiert
werden können (das interessanterweise sowohl mit Prä-, als auch Postfix-Inkrement) und er muss dereferenziert werden können,
wobei das Ergebnis der Dereferenzierung eine Zuweisung aus dem Datentyp, mit dem der Iterator verwendet werden soll,
unterstützen muss. Die Inkrements und Dereferenzierung stellt der GuiIterator
bereit, indem er die entsprechenden
Operatoren implementiert. Interessant ist hier die Unterscheidung zwischen Präfix und Postfix: die Postfix-Variante hat
einen anonymen int
-Parameter. Immer wenn wir ++i
schreiben, ruft der Compiler i.operator++()
auf. Schreiben wir
hingegen i++
, dann wird daraus i.operator++(int)
(wobei der übergebene Wert für den int
unspezifiziert ist). In
unserem Fall muss der Iterator nicht hochzählen, weswegen beide Operatoren gleich sind: sie liefern einfach den Iterator
selbst zurück. Würden wir den Iterator intern verändern (bspw. auf das nächste zu schreibende Element weiterzählen), dann
müsste das Postfix-Inkrement den alten Wert (bspw. als temporäres Objekt) zurückliefern, wohingegen Präfix-Inkrement den
neuen Wert liefert. Dieser kleine, aber gewichtige Unterschied wird gern übersehen.
Die Dereferenzierung des Iterators würde normalerweise eine Referenz auf den zu schreibenden Wert liefern (bspw. eine
int
-Variable), die dann zugewiesen werden kann. In unserem Fall ist das natürlich nicht so einfach: wir wollen ja den
Inhalt des QLabel
anpassen, brauchen also etwas mehr Komplexität. Zu dem Zweck gibt es die interne (aber öffentliche)
Klasse Executor
, die eine Zuweisung aus dem Datentyp des umgebenden Templates unterstützt und den Transfern in das Label
implementiert. Da diese Klasse eine nested class innerhalb des GuiIterator-Templates ist, ist sie selbst auch mit
parametrisiert. GuiIterator<int>::Executor
ist also etwas anderes, als GuiIterator<std::string>::Executor
. Wir bekommen
so ganz bequem Zugriff auf den Templateparameter der umgebenden Klasse und können sicherstellen, dass wir im operator=()
den passenden Parametertypen implementieren. Die eigentliche Implementierung ist dann relativ simpel: wir nutzen
std::ostringstream
, eine Klasse, die das normale Output-Stream-Interface bíetet, aber intern das Ergebnis in einem String
speichert und wieder bereitstellt. Dort bauen wir dann den neuen Text des QLabel
zusammen und schreiben ihn anschließend
zurück.
Ein letztes notwendiges Detail, damit die Standardbibliothek auch mit unserem Iterator spielen will, ist die Spezialisierung
des Templates std::iterator_traits
für unseren Iterator. Mittels der iterator_traits
bekommt die Standardbibliothek zur
Übersetzungszeit einige notwendige Informationen über unseren Iterator raus. In unserem Fall interessant ist nur der
typedef
für iterator_category
. Da wir den als Alias für output_iterator_tag
anlegen, weiß die Standardbibliothek, dass
sie es mit einem OutputIterator
zu tun hat. Alles andere ist dann nicht mehr interessant, da diese Typen nur für
InputIteratoren relevant wären.
Zuguterletzt können wir unseren Iterator im std::copy
als Ziel angeben und die Werte werden wie gewünscht in der
grafischen Oberfläche landen.