2012-12-31 19 views
46

Kann jemand bitte erklären (vorzugsweise mit Englisch) wie std::flush funktioniert?Wie funktioniert std :: flush?

  • Was ist das?
  • Wann würden Sie einen Stream spülen?
  • Warum ist es wichtig?

Vielen Dank.

Antwort

74

Da es nicht beantwortet wurde wasstd::flush passiert ist, hier ist ein paar Details, was es eigentlich ist. std::flush ist ein Manipulator, d. H. Eine Funktion mit einer spezifischen Signatur.Um zu beginnen einfach, kann man sich vorstellen std::flush die Signatur mit

std::ostream& std::flush(std::ostream&); 

Die Wirklichkeit etwas komplexer ist, aber (wenn Sie daran interessiert sind, ist es unten auch erklärt).

Die stream class overload-Ausgabeoperatoren, die Operatoren dieses Formulars verwenden, d. H. Es gibt eine Elementfunktion, die einen Manipulator als Argument verwendet. Der Ausgabeoperator ruft den Manipulator mit dem Objekt selbst:

std::ostream& std::ostream::operator<< (std::ostream& (*manip)(std::ostream&)) { 
    (*manip)(*this); 
    return *this; 
} 

Das heißt, wenn Sie „Ausgang“ std::flush mit zu einem std::ostream, es ist nur die entsprechende Funktion aufruft, dh die beiden folgenden Aussagen sind äquivalent:

std::cout << std::flush; 
std::flush(std::cout); 

Nun std::flush() selbst ist recht einfach: Alles, was es tut, ist std::ostream::flush() zu nennen, das heißt, können Sie die Implementierung vorstellen so etwas wie folgt aussehen:

std::ostream& std::flush(std::ostream& out) { 
    out.flush(); 
    return out; 
} 

Die Funktion std::ostream::flush() ruft technisch std::streambuf::pubsync() im Stream-Puffer (falls vorhanden) auf, der dem Stream zugeordnet ist: Der Stream-Puffer ist verantwortlich für die Pufferung von Zeichen und das Senden von Zeichen an das externe Ziel, wenn der verwendete Puffer überläuft oder wenn die interne Darstellung sollte mit dem externen Ziel synchronisiert werden, dh wenn die Daten geleert werden sollen. Bei einem sequenziellen Stream bedeutet die Synchronisierung mit dem externen Ziel, dass alle gepufferten Zeichen sofort gesendet werden. Das heißt, die Verwendung von std::flush bewirkt, dass der Datenstrompuffer seinen Ausgabepuffer löscht. Wenn beispielsweise Daten in eine Konsole geschrieben werden, werden die Zeichen an dieser Stelle auf der Konsole angezeigt.

Dies kann die Frage aufwerfen: Warum werden Zeichen nicht sofort geschrieben? Die einfache Antwort ist, dass das Schreiben von Zeichen im Allgemeinen ziemlich langsam ist. Die Zeit, die benötigt wird, um eine vernünftige Anzahl von Zeichen zu schreiben, ist jedoch im Wesentlichen identisch mit dem Schreiben von nur einer Stelle. Die Anzahl der Zeichen hängt von vielen Eigenschaften des Betriebssystems, der Dateisysteme usw. ab, aber oft werden bis zu 4k Zeichen ungefähr zur gleichen Zeit geschrieben wie nur ein Zeichen. Daher kann das Puffern von Zeichen, bevor sie unter Verwendung eines Puffers abhängig von den Details des externen Ziels gesendet werden, eine enorme Leistungsverbesserung sein.

Das obige sollte zwei Ihrer drei Fragen beantworten. Die verbleibende Frage ist: Wann würden Sie einen Stream spülen? Die Antwort lautet: Wenn die Zeichen an das externe Ziel geschrieben werden sollen! Dies kann am Ende des Schreibens einer Datei sein (das Schließen einer Datei implizit löscht jedoch den Puffer) oder unmittelbar bevor eine Benutzereingabe angefordert wird (beachten Sie, dass std::cout beim Lesen automatisch gelöscht wird, wenn std::cinstd::istream::tie() 'std::cin ist). Obwohl es einige Gelegenheiten gibt, bei denen Sie explizit einen Stream spülen möchten, finde ich sie ziemlich selten.

Schließlich habe ich versprochen, ein vollständiges Bild zu geben, was std::flush eigentlich ist: Die Ströme Klassen-Templates ist in der Lage mit verschiedenen Charaktertypen zu tun (in der Praxis werden sie mit char arbeiten und wchar_t, so dass sie mit einem anderen Zeichen arbeiten, ist recht kompliziert obwohl machbar, wenn Sie wirklich entschlossen sind).Um in der Lage seine std::flush mit all Instanziierungen Strömen zu verwenden, es geschieht ein Funktions-Template mit einer Signatur so sein:

template <typename cT, typename Traits> 
std::basic_ostream<cT, Traits>& std::flush(std::basic_ostream<cT, Traits>&); 

Wenn std::flush sofort mit einer Instanziierung std::basic_ostream mit ihm nicht wirklich wichtig: Der Compiler leitet die Vorlagenargumente automatisch ab. In Fällen jedoch, in denen diese Funktion nicht zusammen mit etwas erwähnt wird, das die Vorlagenargumentableitung erleichtert, kann der Compiler die Vorlagenargumente nicht ableiten.

+1

Fantastische Antwort. Konnte keine Qualitätsinformationen darüber finden, wie Flush anderswo funktioniert hat. – Michael

22

Standardmäßig ist std::cout gepuffert, und die tatsächliche Ausgabe wird nur gedruckt, sobald der Puffer voll ist oder eine andere Spülung auftritt (z. B. eine neue Zeile im Stream). Manchmal möchten Sie sicherstellen, dass der Druck sofort erfolgt und Sie manuell spülen müssen.

Beispiel: Angenommen, Sie durch Drucken eines einzelnen Punkt einen Fortschrittsbericht melden:

Ohne die Spülung, würden Sie die Ausgabe für eine sehr lange Zeit nicht sehen.

Beachten Sie, dass std::endl einen Zeilenumbruch in einen Stream einfügt und ihn so spült. Da die Spülung nur mßig teuer ist, sollte std::endl nicht übermäßig verwendet werden, wenn die Spülung nicht ausdrücklich erwünscht ist.

+4

Nur eine zusätzliche Anmerkung für Leser: 'cout' ist nicht das einzige Ding Das ist in C++ gepuffert. 'ostreams im Allgemeinen sind standardmäßig gepuffert, was auch' fstream's und ähnliches beinhaltet. – Cornstalks

+0

Auch mit 'cin' wird die Ausgabe vor dem Löschen ausgeführt, nein? – Bateman

4

Ein Stream ist mit etwas verbunden. Bei der Standardausgabe könnte es sich um die Konsole/den Bildschirm handeln oder sie könnte zu einer Pipe oder einer Datei umgeleitet werden. Zwischen Ihrem Programm und z. B. der Festplatte, auf der die Datei gespeichert ist, befindet sich viel Code. Zum Beispiel macht das Betriebssystem Sachen mit jeder Datei oder das Plattenlaufwerk selbst puffert Daten, um es in Blöcken fester Größe schreiben zu können oder um effizienter zu sein.

Wenn Sie den Strom spülen, erzählt er die Sprachbibliotheken, das Betriebssystem und die Hardware, die Sie auf beliebige Zeichen wollen, dass Sie die Ausgabe des ganzen Weg zur Speicherung haben bisher gezwungen. Theoretisch könnte man nach einem "Flush" das Kabel aus der Wand treten und diese Zeichen würden immer noch sicher aufbewahrt werden.

Ich sollte erwähnen, dass die Leute, die die OS-Treiber schreiben oder die Leute, die das Laufwerk entwerfen, frei sind, 'flush' als Vorschlag zu verwenden und sie könnten die Charaktere nicht wirklich herausschreiben. Selbst wenn die Ausgabe geschlossen ist, können sie einige Zeit warten, um sie zu speichern. (Denken Sie daran, dass das Betriebssystem alle möglichen Dinge gleichzeitig erledigt und es effizienter ist, ein oder zwei Sekunden auf die Verarbeitung Ihrer Bytes zu warten.)

Ein Flush ist also eine Art Checkpoint.

Ein weiteres Beispiel: Wenn die Ausgabe an die Konsolenanzeige wird, ein Flush wird sicherstellen, dass die Charaktere bekommen tatsächlich den ganzen Weg hinaus, wo der Benutzer sie sehen kann. Dies ist wichtig, wenn Sie Tastatureingabe erwarten. Wenn Sie denken, dass Sie eine Frage an die Konsole geschrieben haben und sie immer noch irgendwo in einem internen Puffer steckt, weiß der Benutzer nicht, was er antworten soll. Dies ist also ein Fall, in dem die Flush wichtig ist.

8

Hier ist ein kurzes Programm, das Sie schreiben können zu beobachten, was bündig

#include <iostream> 
#include <unistd.h> 

using namespace std; 

int main() { 

    cout << "Line 1..." << flush; 

    usleep(500000); 

    cout << "\nLine 2" << endl; 

    cout << "Line 3" << endl ; 

    return 0; 
} 

Führen Sie dieses Programm macht: Sie werden feststellen, dass es Zeile druckt 1, Pausen, dann druckt Zeile 2 und 3. Entfernen Sie nun den Flush-Aufruf und führen Sie das Programm erneut - Sie werden feststellen, dass das Programm pausiert und druckt dann alle 3 Zeilen gleichzeitig. Die erste Zeile wird gepuffert, bevor das Programm pausiert, aber weil der Puffer nie geleert wird, wird Zeile 1 erst beim letzten Aufruf von Zeile 2 ausgegeben.