Ich habe eine sehr einfache Dateiverwaltungsdatenbank geschrieben, die im Prinzip so aussieht:
%Vor% Nun würde ich gerne alle in der Datenbank enthaltenen Dateien durchlaufen wie mit std::iterator
:
Ich habe Antworten auf ähnliche Fragen gelesen, einige schlagen vor, std::iterator
abzuleiten, einige verwenden std::iterator_traits
, aber ich verstehe nicht wirklich, wie das geht. Was kann möglicherweise schiefgehen, wenn Sie versuchen, einen benutzerdefinierten Iterator zu implementieren? Und was ist ein einfacher und doch eleganter Weg?
BEARBEITEN: Bitte denken Sie nicht an Boost, meine Frage ist eher konzeptueller Natur.
EDIT 2:
Die FileDB funktioniert so:
rootDir
foo2
fooN
barM
Im Grunde kann ich eine Datei mit ihrem Namen finden.
Da sich mein Container nicht im Speicher befindet, habe ich keine Zeiger auf seine Daten. Meine Idee war also, den Pfad der Datei im Iterator zu speichern. Auf diese Weise kann ich operator==
mit einem String-Vergleich implementieren, da die Pfade eindeutig sein sollten. Der von fileDB.end()
zurückgegebene Iterator wäre eine leere Zeichenfolge und operator*
würde fileDB::loadFile()
mit seinem Dateipfad aufrufen.
Meine größte Sorge ist über operator++
. Nachdem ich den Dateinamen gefunden habe, kann ich das enthaltende Verzeichnis finden und nach der nächsten Datei suchen, aber das ist wirklich ineffektiv. Irgendwelche Ideen, wie man das macht? Oder bin ich völlig falsch mit meinem ganzen Konzept?
Schreiben Sie Iteratoren selbst ist selten hübsch. Die beste Möglichkeit, Ihren Klassen einen Iterator hinzuzufügen, ist die Wiederverwendung eines vorhandenen Iterators. Sie sollten wissen, dass Zeiger zum Beispiel genauso gut sind wie Iteratoren in C ++, so dass es viele Möglichkeiten gibt, einen Iterator bereitzustellen, ohne dass Sie selbst einen eigenen schreiben müssen.
Dies ist im Grunde, wie C ++ auf viele Arten funktioniert. Es versucht, die Sprache für die Endbenutzer entbehrlich und einfach zu machen, indem es die Bibliotheksautoren stark belastet. I.e. Bibliotheksautoren können all die unpraktischen Dinge schreiben, also muss der Endbenutzer nicht. Iteratoren sind normalerweise ein Teil einer Bibliothek.
Nachdem das gesagt wurde, kommt hier der eigentliche hässliche Teil:
Um eigene Iteratoren schreiben zu können, müssen Sie Folgendes beachten:
Typmerkmale:
Typeigenschaften sind ein einfacher Mechanismus, um zusätzlichen Informationen zu Typen in C ++ hinzuzufügen, die sogar mit Typen funktionieren, die selbst nicht geändert werden können. Zum Beispiel ist es für einen Iterator wichtig zu wissen, worüber es iteriert (d. H. Den enthaltenen Typ). Der Weg, diese Information für einen gegebenen Iterator zu erhalten, hängt sehr vom Iterator ab. Für Iteratoren, bei denen es sich tatsächlich um Objekte handelt, können Sie typedefs in der Klasse hinzufügen und diese verwenden, aber für Iteratoren, bei denen es sich um Zeiger handelt, müssen Sie sie aus dem Zeigertyp ableiten. Um dies zu ermöglichen, wird die Information stattdessen in einem Typmerkmal gespeichert, so dass es einen einzigen Ort gibt, an dem ein Code diese Information sehen kann. Dies ist das Merkmal std::iterator_traits
type.
std::iterator_traits
arbeitet an allem, was sowohl von der std::iterator
Vorlage als auch von jeder Art von Pointer abgeleitet ist, ohne irgendwelche Feinabstimmungen vorzunehmen. Oft ist es am besten, std::iterator
als Basis zu verwenden, um zu vermeiden, dass Sie Ihre eigene Merkmalspezialisierung schreiben müssen. Für den Fall, dass Sie dies nicht tun können, ist es immer noch möglich, die notwendigen Eigenschaften bereitzustellen, aber es wird schwieriger sein.
Tag-Klassen und Iterator-Typen:
In C ++ gibt es verschiedene Arten von Iteratoren, die ein unterschiedliches Verhalten haben und viele verschiedene Dinge nicht können. Werfen Sie einen Blick auf Ссылка , um zu sehen, welche Art von Iteratoren verfügbar sind und was sie tun können. Die Diagramme sollen nicht in einer objektorientierten Weise (d. H. Ein input_iterator
ist weder eine Unterklasse noch eine Basisklasse von forward_iterator
) gemeint sein, sondern eher als eine API-Art der Ableitung. I.e. Sie können alle Algorithmen verwenden, die für einen Eingabe-Iterator auch mit einem Vorwärts-Iterator geschrieben wurden. Die Tabelle auf der Seite wird Ihnen sagen, welche Methoden Sie für jede Kategorie bereitstellen müssen.
Da es sich bei diesen Kategorien nicht wirklich um Unterklassen handelt (dies sollte nicht der Fall sein, besonders wenn sie aus verschiedenen Arten von Sammlungen stammen), wird ein anderer Mechanismus verwendet, um die Fähigkeiten jedes Iterators zu identifizieren. Eine leere Tag-Klasse ist auch in std::iterator_traits
enthalten und beschreibt jeden Iterator, der angibt, was dieser Iterator kann und was nicht. Wenn Sie keine eigenen Merkmale schreiben, müssen Sie diese Tag-Klasse bei der Instanziierung der Vorlage std::iterator
zur Verfügung stellen.
Beispiel:
Dieses Beispiel stammt aus dem Abschnitt cplusplus.com über Iteratoren:
%Vor% Dieser Iterator ist nicht wirklich sinnvoll, da er nur einen Zeiger umschließt, der auch direkt verwendet werden könnte. Es kann jedoch als Erklärung dienen. Der Iterator wird von std::iterator
als input_iterator
abgeleitet, indem das entsprechende Tag angegeben wird. Außerdem wird der Vorlage gesagt, dass dieser Iterator über int
s iteriert. Alle anderen Typen, die difference_type
, reference
, poiner
usw. benötigen, werden automatisch von der Vorlage berücksichtigt. In manchen Fällen kann es sinnvoll sein, einige dieser Typen manuell zu ändern (zum Beispiel muss std::shared_ptr
manchmal als pointer
verwendet werden). Auch die Eigenschaften, die für diesen Iterator benötigt werden, existieren automatisch, da sie bereits von std::iterator
abgeleitet sind und die std::iterator_traits
wissen, wo sie alle notwendigen Informationen finden können.