2015-06-02 7 views
21

Für E/A-Arbeit muss ich N Bytes in einen Puffer lesen. N ist zur Laufzeit bekannt (keine Kompilierzeit). Die Puffergröße wird sich niemals ändern. Der Puffer wird an andere Routinen übergeben, um sie zu komprimieren, zu verschlüsseln usw.: Es ist nur eine Folge von Bytes, nichts höher als das.Modernes C++ - Idiom zum Zuweisen/Freigeben eines E/A-Puffers

In C würde ich den Puffer mit malloc und dann free es zuweisen, wenn ich fertig bin. Allerdings ist mein Code modern C++, sicherlich keine malloc s an Ort und Stelle, und sehr wenige rohe new und delete: Ich mache starken Gebrauch von RAII und shared_ptr. Keine dieser Techniken scheint jedoch für diesen Puffer geeignet zu sein. Es ist nur ein Puffer fester Länge von Bytes, um E/A zu empfangen und seinen Inhalt verfügbar zu machen.

Gibt es ein modernes C++ - Idiom, das elegant ist? Oder sollte ich für diesen Aspekt einfach bei gutem alten malloc bleiben?

+13

'std :: vector'. Immer 'std :: vector'. naja ... fast außer du hast einen sehr guten Grund. – bolov

+0

Ab C++ 11 können Sie sogar mit '.data()' auf den rohen Speicher hinter einem Vektor zugreifen. –

+3

@StefanoSanfilippo: Immer mit '& front()' (nur für nicht-leere, gewährt) –

Antwort

3

In C++ 14, gibt es eine sehr syntaktisch saubere Art und Weise zu erreichen, was Sie wollen:

size_t n = /* size of buffer */; 
auto buf_ptr = std::make_unique<uint8_t[]>(n); 
auto nr = ::read(STDIN_FILENO, buf_ptr.get(), n); 
auto nw = ::write(STDOUT_FILENO, buf_ptr.get(), nr); 
// etc. 
// buffer is freed automatically when buf_ptr goes out of scope 

Beachten Sie, dass das obige Konstrukt Wert initialisieren (Null aus), um den Puffer. Wenn Sie die Initialisierung überspringen möchten, um ein paar Zyklen zu speichern, müssen Sie das etwas hässlichere Formular von lisyarus verwenden:

+0

Ein weiterer großer Vorteil von Matts Antwort ist, dass die Zerstörungszeit massiv verbessert wird. Ich habe vor kurzem einige Code von der Verwendung eines großen Vektors als Puffer, mit Zerstörungszeit in Sekunden gemessen, zu einem unique_ptr mit effektiv sofortiger Zerstörung umgewandelt. – Nanki

+1

@ Nanki Das ist schwer zu glauben; Kannst du erklären, was das verursacht haben könnte? – SRobertJames

+0

Es stellte sich heraus, dass dies auf einen bekannten Fehler in VS2015 während des Debugging zurückzuführen war und den Freigabecode nicht beeinflusst hat - und es wurde bereits behoben. – Nanki

11

Ja, einfach:

std::vector<char> myBuffer(N); 
5

Ich denke, die gemeinsam eine std::vector dafür zu verwenden. Die Vorteile der Verwendung von std::vector über einen manuell zugewiesenen Puffer char sind die Kopiesemantik (zur Übergabe an Funktionen, die die Daten für ihre eigenen Zwecke ändern oder die Daten an eine aufrufende Funktion zurückgeben möchten).

Auch ein std::vector kennt seine eigene Größe, die Anzahl der Parameter zu reduzieren, die an Verarbeitungsfunktionen übergeben werden müssen und eine Fehlerquelle beseitigen.

Sie haben die vollständige Kontrolle darüber, wie die Daten an andere Funktionen übergeben wird - entweder durch Referenz oder const gegebenenfalls verweisen.

Wenn Sie eine ältere c-Stil Funktion mit einem einfachen char* und Länge anrufen möchten, können Sie einfach das auch tun:

// pass by const reference to preserve data 
void print_data(const std::vector<char>& buf) 
{ 
    std::cout.fill('0'); 
    std::cout << "0x"; 
    for(auto c: buf) 
     std::cout << std::setw(2) << std::hex << int(c); 
    std::cout << '\n'; 
} 

// pass by reference to modify data 
void process_data(std::vector<char>& buf) 
{ 
    for(auto& c: buf) 
     c += 1; 
} 

// pass by copy to modify data for another purpose 
void reinterpret_data(std::vector<char> buf) 
{ 
    // original data not changed 
    process_data(buf); 
    print_data(buf); 
} 

void legacy_function(const char* buf, std::size_t length) 
{ 
    // stuff 
} 

int main() 
{ 
    std::ifstream ifs("file.txt"); 

    // 24 character contiguous buffer 
    std::vector<char> buf(24); 

    while(ifs.read(buf.data(), buf.size())) 
    { 
     // changes data (pass by reference) 
     process_data(buf); 

     // modifies data internally (pass by value) 
     reinterpret_data(buf); 

     // non-modifying function (pass by const ref) 
     print_data(buf); 

     legacy_function(buf.data(), buf.size()); 
    } 
} 
+0

Ich glaube nicht, dass mir das wirklich hilft. Der Vektor muss an andere Funktionen übergeben werden, also muss ich ihn mit neuen zuweisen und dann freigeben. Ist ein Vektor wirklich für diese Art von I/O entworfen? Ist der Datenblock garantiert? – SRobertJames

+0

Ja, 'std :: vectors Speicher wird als fortlaufend angegeben. Und dank der Verschiebungssemantik ist es auch effizient, den Vektor ringsum nach Wert zu übergeben, ohne Referenzsemantik (und somit Speicherverwaltung) zu benötigen. –

+3

@SRobertJames Sie müssen Ihren * Vektor * nicht dynamisch zuweisen, um ihn anderen Funktionen zu übergeben.In der Regel übergeben Sie * Referenz * oder * const Referenz *. Ich habe die Antwort aktualisiert, um die Verwendung zu demonstrieren. Auch 'std :: vector' ist garantiert ein zusammenhängender Block. – Galik

15

Grundsätzlich gibt es zwei Haupt-C++ - Wege zur Auswahl:

  • std::vector
  • std::unique_ptr

Ich würde die zweite bevorzugen, da Sie nicht alle die automatische Größenänderung Zeug in std::vector benötigen, und Sie brauchen keinen Container - Sie brauchen nur einen Puffer.

std::unique_ptr hat eine Spezialisierung für dynamische Arrays: std::unique_ptr<int[]> nennen delete [] in seinem destructor, und werden Sie mit entsprechenden operator [].

Wenn Sie den Code wollen:

std::unique_ptr<char[]> buffer(new char [size]); 
some_io_function(buffer.get(), size); // get() returnes raw pointer 

Unfortunatelly, ist es nicht eine Möglichkeit haben, die Größe des Puffers abrufen, so dass Sie es in einer Variablen zu speichern haben.Wenn es Sie verwirrt, dann tun std::vector die Arbeit:

std::vector<char> buffer(size); 
some_io_function(buffer.data(), buffer.size()); // data() returnes raw pointer 

Wenn Sie den Puffer um passieren wollen, hängt davon ab, wie genau Sie es tun.

Betrachten Sie den folgenden Fall: Der Puffer wird irgendwo gefüllt, dann woanders verarbeitet, für einige Zeit gespeichert, dann irgendwo geschrieben und zerstört. Es passiert, dass Sie nie wirklich zwei Stellen im Code brauchen, um den Puffer zu besitzen, und Sie können einfach std::move von Ort zu Ort gehen. Für diesen Anwendungsfall funktioniert std::unique_ptr perfekt und schützt Sie vor gelegentlichen Kopieren des Puffers (während Sie mit std::vector können Sie es versehentlich kopieren, und kein Fehler oder Warnung wird auftreten).

Wenn Sie im Gegensatz dazu mehrere Stellen im Code benötigen, um denselben Puffer zu speichern (vielleicht wird er an mehr als einer Stelle gleichzeitig gefüllt/verarbeitet), benötigen Sie definitiv std::shared_ptr. Leider hat es nicht Array-artige Spezialisierung, so dass Sie entsprechende deleter passieren müssen:

std::shared_ptr<char> buffer(new char[size], std::default_delete<char[]>()); 

Die dritte Option ist, wenn Sie wirklich den Puffer kopieren müssen. Dann wird std::vector einfacher sein. Aber, wie ich bereits erwähnt habe, ich fühle, dass es nicht der beste Weg ist. Außerdem können Sie immer den Puffer halten, kopieren, indem std::unique_ptr oder std::shared_ptr manuell, was eindeutig dokumentiert Ihre Absicht:

std::uniqure_ptr<char[]> buffer_copy(new char[size]); 
std::copy(buffer.get(), buffer.get() + size, buffer_copy.get()); 
+1

Die Verwendung von 'std :: unique_ptr' ist möglicherweise nicht die beste Alternative für einen Puffer, der wiederverwendet und an mehrere Funktionen übergeben werden soll. –

+0

@JoachimPileborg nichts in der Frage über mehrere Funktionen gesagt. Aus meiner Erfahrung werden IO-Puffer normalerweise nicht auf diese Weise verwendet. Wenn es noch benötigt wird, dann wird 'std :: vector' auch nicht funktionieren - Sie müssen zum Beispiel' std :: shared_ptr' verwenden. – lisyarus

+0

"Der Puffer wird an andere Routinen übergeben ...". In Bezug auf die Verwendung eines Vektors ist es zumindest nicht dafür ausgelegt, primär nach Wert weitergegeben zu werden. –

1

Verwenden

std::vector<char> buffer(N) 

Wenn die Größe des Puffers ändert sich nie, können Sie es als ein Array, indem Sie dies tun:

char * bufPtr = &buffer[0]; 

Dies wird auch in C++ 03 funktionieren. Sehen Sie diesen Kommentar https://stackoverflow.com/a/247764/1219722 für Details darüber, warum das sicher ist.