Text-Double-Buffering

Als Entwickler freue ich mich natürlich sehr, dass meine Erfindung letztlich dann doch in den C++17 Standard Einzug gefunden hat.

std::string_view

OK, Spaß bei Seite … das haben wohl auch schon zehntausende Entwickler vor mir gebraucht und darauf hin erfunden. Aber trotzdem erinnere ich mich noch zurück an das Jahr 2007, als mir bei der Arbeit mit std::string unmittelbar auffiel, dass dieses unglaublich wichtige Feature im Standard fehlte.

Ein string_view repräsentiert einen Teil eines Strings. Während übliche C-char arrays und std::strings Null-terminiert als ganzes im Speicher liegen, kann man sich einen View als einen Pointer an eine beliebige Stelle im String und dazu eine Leselänge vorstellen.

struct simple_string_view
{
	char const*	begin_of_view;
	size_t		length_of_view;
	
	/* ... and some more references or reference-counters */
}

(Natürlich kann man im Iterator-Style auch ein end_of_view anstatt einer length_of_view)

Das hat einen riesen Vorteil: Wir können aus einem großen Datenpuffer unzählige String-Views herausziehen.

Ein schönes Beispiel ist ein XML - Parser, der einen XML-Block in einen Baum aus Tag-Strings und Attribut-Strings aufbaut.

Klassische C++ Bibliotheken erzeugen für jeden Knoten im XML Baum einen std::string und allokieren sich bei großen Dokumenten zum Hugo.

Der String-View verweist immer nur auf Ausschnitte des Gesamtdokuments und ist somit unglaublich schneller mit dem Erstellen solcher Strukturen fertig.

Aber die Genialität von string_view soll hier gar nicht das Thema sein.

Problem: APIs brauchen oft Null-terminierte Strings

Und das kann dann wieder etwas dumm werden, denn der View kann nicht irgendwo in der Mitte seines referenzierten Puffers eine Null reinschreiben.

Die einzige Lösung ist dann für solche Fälle die benötigten Daten in einen temporären Puffer zu kopieren und eben diesen Puffer mit der API zu nutzen.

Damit wir jetzt nicht unsere frisch gewonnene Allokierungsreduktion wieder verlieren, nutze ich gerne eine doppelte Strategie:
Wenn möglich wird ein Stack

  • Puffer genutzt, der nicht aufwendig allokiert werden muss und nur übergroße Textstücke kommen auf den Heap.
void do_something(struct simple_string_view* view)
{
  char localbuffer[4096];
  char* heapbuffer = NULL;
  char* buffer;
  
  if(view->length_of_view + 1 > sizeof(length_of_view))
  {
    /* large buffer is allocated on heap */
    heapbuffer = malloc(view->length_of_view + 1);
    buffer = heapbuffer;	
  }
  else
  {
    /* small buffer is allocated on stack*/
    buffer = localbuffer;
  }
  memcpy(buffer, view->begin_of_view, view->length_of_view);
  buffer[view->length_of_view] = 0;
  
  call_my_null_terminated_function(buffer);
  
  if(heapbuffer != NULL)
  {
    free(heapbuffer);
  }
}

Ganz ähnlich zur String Debatte zeichnet sich auch hier ab: Viele Bibliotheken haben ihren string_view schon teils vor 30 Jahren erfunden und implementiert.

Dass C++ erst seit 2017 hier nachzieht, ist leider viel zu spät … aber trotzdem ein wichtiger Ansatz. Vielleicht schaffen wir es ja in den nächsten 20 Jahren unsere eigenen Implementierungen durch den Standard zu ersetzen.

However … nicht jede Implementierung ist perfekt. Und wir sprechen hier nur von C++ und nicht von reinem C, wo ich dieses Feature ebenfalls schmerzhaft vermisse.

Das GATE Projekt definiert eine C-String Klasse, die beides sein kann, eine String-View oder ein allokierter String-Puffer.


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!