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
#include <Qt/qapplication.h>
#include <Qt/qmainwindow.h>
#include <Qt/qlabel.h>
#include <Qt/qgridlayout.h>
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include <iterator>
#include <sstream>
template <typename DataT> class GuiIterator
{
public:
GuiIterator(QLabel *l)
: mExecutor(l)
{
}
class Executor
{
QLabel *label;
public:
Executor(QLabel *l)
: label(l)
{
}
Executor &operator=(DataT const &value)
{
std::ostringstream s;
s << label->text().toStdString();
s << "\n";
s << value;
label->setText(s.str().c_str());
return *this;
}
};
GuiIterator &operator++()
{
return *this;
}
GuiIterator &operator++(int)
{
return *this;
}
Executor &operator*()
{
return mExecutor;
}
private:
Executor mExecutor;
};
namespace std
{
template <typename T> class iterator_traits<GuiIterator<T> >
{
public:
typedef void difference_type;
typedef void value_type;
typedef void pointer;
typedef void reference;
typedef output_iterator_tag iterator_category;
};
}
int main(int argc, char *argv[])
{
QApplication qapp(argc, argv);
QMainWindow mw;
QLabel label;
QFont font;
font.setPointSize(16);
font.setBold(true);
label.setFont(font);
mw.setCentralWidget(&label);
std::vector<int> v;
v.push_back(2);
v.push_back(7);
v.push_back(4);
std::copy(v.begin(), v.end(), GuiIterator<int>(&label));
std::list<std::string> l;
l.push_back("Eins");
l.push_back("Zwo");
l.push_back("Drei");
std::copy(l.begin(), l.end(), GuiIterator<std::string>(&label));
mw.show();
qapp.exec();
}
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.