Implementieren eines "String-Pools", der garantiert nicht verschoben wird

8

Ich brauche ein "String Pool" -Objekt, in das ich wiederholt eine "Folge von Zeichen" einfügen kann (ich benutze diesen Ausdruck als "String", ohne ihn mit std :: string oder einem C-String zu verwechseln), erhalte einen Zeiger zu der Sequenz und garantiert werden, dass der Zeiger nicht ungültig wird, wenn / wenn der Pool wachsen muss. Die Verwendung eines einfachen std::string als Pool funktioniert nicht, da der String neu zugeordnet werden kann, wenn seine ursprüngliche Kapazität überschritten wird, wodurch alle vorherigen Zeiger ungültig werden.

Der Pool wird nicht ohne Grenzen wachsen - es gibt klar definierte Punkte, an denen ich eine clear() Methode nennen werde - aber ich möchte auch keine maximale Kapazität dafür reservieren. Es sollte wachsen können, ohne sich zu bewegen.

Eine Möglichkeit, die ich in Betracht ziehe, ist, jede neue Zeichenfolge in ein forward_list<string> einzufügen und begin()->c_str() zu erhalten. Ein anderer fügt in ein unordered_set<string> ein, aber ich habe Schwierigkeiten, herauszufinden, was passiert, wenn ein unordered_set wachsen muss. Die dritte Möglichkeit, die ich in Betracht ziehe (weniger enthusiastisch), ist das Rollen meiner eigenen Kette von 1K-Puffern, in die ich die Folge von Zeichen verkette. Das hat den Vorteil (denke ich), die höchste Leistung zu haben, was eine Voraussetzung für dieses Projekt ist.

Ich wäre daran interessiert zu hören, wie andere empfehlen würden, sich diesem Thema zu nähern.

UPDATE 1: bearbeitet, um meine Verwendung der Phrase "Folge von Zeichen" zu verdeutlichen, um dem allgemeinen Begriff einer "Zeichenfolge" zu entsprechen, ohne entweder std :: string oder null-terminiertes Zeichen zu implizieren Array.

    
Chap 05.01.2014, 20:00
quelle

3 Antworten

7

Ich habe diesen Ansatz in der Vergangenheit benutzt:

%Vor%

Offensichtlich, wenn Sie das Set löschen möchten / müssen, würden Sie es in einem größeren Umfang verfügbar machen.

Für noch mehr Effizienz bewegen / setzen Sie die Strings in das Set.

Aktualisieren Ich habe diesen Ansatz der Vollständigkeit halber hinzugefügt. Sehen Sie Live auf Coliru

%Vor%     
sehe 06.01.2014, 23:24
quelle
1

Ja, Sie müssen eine Pufferliste schreiben. Nein, mach nicht all die harte Arbeit selbst.

Die zugrunde liegende Datenstruktur sollte ein std::vector<std::string> sein. Mit einer (Vorwärts-) Liste kaufen Sie nicht viel. Wenn der Vektor in der Größe geändert wird, werden die Zeichenfolgen effizient verschoben. std::forward_list<std::string> . Selbst wenn die Größe der Liste geändert wird, bleiben die Zeichenfolgen selbst erhalten. Das Iterieren der Liste wird nur für .clear benötigt, sodass die Listenleistung nicht kritisch ist.

Die Wrapperklasse sollte die Hinzufügung neuer Zeichenfolgen abstrahieren. Eine neue Zeichenfolge sollte hinzugefügt werden, wenn die Kapazität der letzten Zeichenfolge nicht ausreicht, um die neue Zeichenfolge hinzuzufügen. Wenn Sie eine neue Zeichenfolge hinzufügen, muss reserve der gesamte Speicher sein, den ein Chunk benötigt - dies stellt sicher, dass die Kapazität groß genug ist, um spätere Neuzuweisungen zu verhindern.

Dieses Setup kann Speicherplatz verschwenden, wenn eine große neue Zuweisung die Verwendung eines neuen Chunks erzwingt, wodurch ein Teil eines älteren Chunks nicht verwendet wird. Sie könnten sich natürlich die verbleibende Größe in den letzten N Blöcken für einen kleinen Wert von N merken, so dass diese Blöcke möglicherweise noch im Cache sind. Aber es ist durchaus möglich, dass in deiner App N = 5 schon zu groß wäre.

    
MSalters 06.01.2014 23:15
quelle
0

Überarbeiten Sie Ihre Anforderungen:

  • Sie können Elemente verschieben
  • Erhalten Sie einen Iterator am Anfang der Sequenz
  • Iteratoren sollten nicht ungültig gemacht werden, wenn die Sequenz größer wird
  • Kann clear der Sequenz
  • Reservieren Sie keine maximale Kapazität

Es scheint, dass std::list<char> perfekt in diese Liste von Anforderungen passt. Natürlich brauchen Sie vielleicht einen Wrapper um die Klasse, damit sie sich genauso verhält wie std::string , aber das hängt wirklich davon ab, wie Sie die Daten manipulieren.

Und hier ist, wie gut es den Anforderungen entspricht:

  • Um Elemente zu verschieben, können Sie die Funktionen push_back und emplace_back verwenden.

  • std::begin(container) oder die Memberfunktion begin ruft den Iterator auf das erste Element der Sequenz ab.

  • Das Hinzufügen, Entfernen und Verschieben der Elemente in der Liste oder über mehrere Listen macht die Iteratoren nicht ungültig. Ein Iterator wird nur dann ungültig gemacht, wenn das entsprechende Element gelöscht wird.

  • Um die Reihenfolge zu löschen, können Sie die Elementfunktion clear verwenden.

  • Die meiste Zeit ist es als doppelt verknüpfte Liste implementiert, daher ist keine Kapazität reserviert.

Seit std::list scheint Speicher ineffizient zu sein (obwohl der Standard weder die Größe noch seine Implementierung angibt), es ist richtig hinzuzufügen, dass Sie auch std::deque<char> mit fast die gleiche Schnittstelle wie oben. Der einzige Unterschied ist, dass std::deque möglicherweise ungenutzten Speicher reserviert.

    
Shoe 05.01.2014 20:53
quelle