wake-up-neo.net

Ein dynamischer Puffertyp in C++?

Ich bin nicht gerade ein C++ - Neuling, aber ich hatte in der Vergangenheit wenig ernsthafte Auseinandersetzungen damit, so dass meine Kenntnisse über seine Einrichtungen eher lückenhaft sind.

Ich schreibe ein schnelles Proof-of-Concept-Programm in C++ und benötige einen dynamisch großen Puffer mit Binärdaten. Das heißt, ich empfange Daten von einem Netzwerk-Socket und weiß nicht, wie viel es sein wird (obwohl nicht mehr als ein paar MB). Ich könnte selbst einen solchen Puffer schreiben, aber warum sollte ich mich darum kümmern, wenn die Standardbibliothek wahrscheinlich bereits etwas enthält? Ich verwende VS2008, daher ist eine Microsoft-spezifische Erweiterung für mich in Ordnung. Ich brauche nur vier Operationen:

  • Erstellen Sie den Puffer
  • Daten in den Puffer schreiben (Binär-Junk, nicht nullterminiert)
  • Holen Sie sich die geschriebenen Daten als Char-Array (zusammen mit seiner Länge)
  • Geben Sie den Puffer frei

Wie heißt die Klasse/Funktionsgruppe/was auch immer ich brauche?

Hinzugefügt: Mehrere Stimmen gehen an std::vector. Alles schön und gut, aber ich möchte nicht mehrere MB Daten byteweise übertragen. Der Socket gibt mir Daten in wenigen KB großen Blöcken, daher möchte ich sie alle auf einmal schreiben. Außerdem muss ich am Ende die Daten als einfaches Zeichen * abrufen, da ich den gesamten Blob unverändert an einige Win32-API-Funktionen weitergeben muss.

23
Vilx-

Sie möchten einen std::vector :

std::vector<char> myData;

vector wird seinen Speicher automatisch für Sie reservieren und freigeben. Verwenden Sie Push_back, um neue Daten hinzuzufügen (vector ändert sich ggf. für Sie), und der Indexierungsoperator [], um Daten abzurufen.

Wenn Sie zu irgendeinem Zeitpunkt erraten können, wie viel Speicher Sie benötigen, schlage ich vor, reserve aufzurufen, damit nachfolgende Push_backs nicht so viel neu zuweisen müssen.

Wenn Sie einen Teil des Speichers einlesen und an Ihren Puffer anhängen möchten, könnte dies am einfachsten sein:

std::vector<char> myData;
for (;;) {
    const int BufferSize = 1024;
    char rawBuffer[BufferSize];

    const unsigned bytesRead = get_network_data(rawBuffer, sizeof(rawBuffer));
    if (bytesRead <= 0) {
        break;
    }

    myData.insert(myData.end(), rawBuffer, rawBuffer + bytesRead);
}

myData enthält jetzt alle gelesenen Daten und liest Stück für Stück. Wir kopieren jedoch zweimal.

Wir versuchen es stattdessen so:

std::vector<char> myData;
for (;;) {
    const int BufferSize = 1024;

    const size_t oldSize = myData.size();
    myData.resize(myData.size() + BufferSize);        

    const unsigned bytesRead = get_network_data(&myData[oldSize], BufferSize);
    myData.resize(oldSize + bytesRead);

    if (bytesRead == 0) {
        break;
    }
}

Was direkt in den Puffer einliest, auf Kosten einer gelegentlichen Überbelegung.

Dies kann durch z. Verdoppelung der Vektorgröße für jede Größenänderung, um die Größenänderung zu amortisieren, wie dies bei der ersten Lösung implizit der Fall ist. Und natürlich können Sie reserve() einen viel größeren Puffer vorab bereitstellen, wenn Sie über die wahrscheinliche Größe des endgültigen Puffers im Voraus Bescheid wissen, um die Größenveränderung zu minimieren.

Beide sind dem Leser als Übung überlassen. :)

Schließlich, wenn Sie Ihre Daten als Raw-Array behandeln müssen:

some_c_function(myData.data(), myData.size());

std::vector ist garantiert zusammenhängend.

39
GManNickG
std::vector<unsigned char> buffer;

Jedes Push_back fügt am Ende ein neues Zeichen hinzu (falls erforderlich, eine neue Zuordnung). Sie können Reserve anrufen, um die Anzahl der Zuordnungen zu minimieren, wenn Sie ungefähr wissen, wie viele Daten Sie erwarten.

buffer.reserve(1000000);

Wenn Sie so etwas haben:

unsigned char buffer[1000];
std::vector<unsigned char> vec(buffer, buffer + 1000);
9

std::string würde dafür funktionieren:

  • Es unterstützt eingebettete Nullen.
  • Sie können Multi-Byte-Datenblöcke an das Objekt anhängen, indem Sie append() mit einem Zeiger und einer Länge aufrufen.
  • Sie können den Inhalt als Char-Array erhalten, indem Sie data() und die aktuelle Länge aufrufen, indem Sie size() oder length() aufrufen.
  • Das Freigeben des Puffers wird automatisch vom Destruktor gehandhabt. Sie können jedoch auch clear() aufrufen, um dessen Inhalt zu löschen, ohne ihn zu zerstören.
7
Wyzard

Noch eine Stimme für std :: vector. Minimaler Code, überspringt die zusätzliche Kopie von GMans Code:

std::vector<char> buffer;
static const size_t MaxBytesPerRecv = 1024;
size_t bytesRead;
do
{
    const size_t oldSize = buffer.size();

    buffer.resize(oldSize + MaxBytesPerRecv);
    bytesRead = receive(&buffer[oldSize], MaxBytesPerRecv); // pseudo, as is the case with winsock recv() functions, they get a buffer and maximum bytes to write to the buffer

    myData.resize(oldSize + bytesRead); // shrink the vector, this is practically no-op - it only modifies the internal size, no data is moved/freed
} while (bytesRead > 0);

Wenn Sie WinAPI-Funktionen aufrufen möchten, verwenden Sie & buffer [0] (ja, es ist etwas unbeholfen, aber so ist es), um an die char * -Argumente, buffer.size () als Länge zu übergeben. 

Eine letzte Anmerkung: Sie können std :: string anstelle von std :: vector verwenden. Es sollte keinen Unterschied geben (außer Sie können buffer.data () anstelle von & buffer [0] schreiben, wenn der Puffer ein String ist.)

6
sbk

Ich würde einen Blick auf Boost basic_streambuf werfen, der für diesen Zweck entwickelt wurde. Wenn Sie Boost nicht verwenden können (wollen oder wollen), würde ich std::basic_streambuf in Betracht ziehen, was ziemlich ähnlich ist, aber etwas mehr Arbeit erfordert. In beiden Fällen leiten Sie im Wesentlichen von dieser Basisklasse ab und überladen underflow(), um Daten aus dem Socket in den Puffer zu lesen. Normalerweise hängen Sie einen std::istream an den Puffer an, so dass der andere Code ungefähr so ​​liest, wie er es von der Tastatur (oder was auch immer) eingeben würde.

4
Jerry Coffin

Eine Alternative, die nicht von STL stammt, aber nützlich sein könnte - Boost.Circular Puffer

2
Sergei Kurenkov

Verwenden Sie std :: vector , ein wachsendes Array, das garantiert, dass der Speicher zusammenhängend ist (Ihr dritter Punkt).

1
Xavier Nodet

In Bezug auf Ihren Kommentar "Ich sehe keine Anhänge ()", ist das Inesertieren am Ende dasselbe.

vec.insert (vec.end,

0

Wenn Sie std :: vector verwenden, verwenden Sie es lediglich zur Verwaltung des Rohspeichers. Sie könnten nur malloc den größten Puffer, von dem Sie glauben, dass Sie ihn benötigen, und den Schreib-Offset verfolgen können. Gesamtzahl der bisher gelesenen Bytes (sie sind dasselbe). Wenn Sie zum Ende kommen ... entweder realloc oder wählen Sie einen Weg zum Scheitern.

Ich weiß, es ist nicht sehr C++, aber dies ist ein einfaches Problem und die anderen Vorschläge scheinen schwergewichtig zu sein, um eine unnötige Kopie einzuführen.

0
Useless