Wie funktioniert std :: flush?

84

Kann jemand bitte erklären (vorzugsweise in einfachem Englisch), wie es std::flushfunktioniert?

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

Vielen Dank.

Dasaru
quelle

Antworten:

137

Da nicht beantwortet wurde, was std::flush gerade ist, finden Sie hier einige Details dazu, was es tatsächlich ist. std::flushist ein Manipulator , dh eine Funktion mit einer bestimmten Signatur. Um einfach zu beginnen, können Sie sich vorstellen std::flush, die Signatur zu haben

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

Die Realität ist jedoch etwas komplexer (wenn Sie interessiert sind, wird dies auch unten erklärt).

Die Stream-Klasse überladen Ausgabeoperatoren, die Operatoren dieser Form verwenden, dh es gibt eine Mitgliedsfunktion, die einen Manipulator als Argument verwendet. Der Ausgabeoperator ruft den Manipulator mit dem Objekt selbst auf:

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

Das heißt, wenn Sie std::flushmit einem "ausgeben" std::ostream, wird nur die entsprechende Funktion aufgerufen, dh die folgenden zwei Anweisungen sind äquivalent:

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

Nun ist es std::flush()selbst ziemlich einfach: Alles, was es tut, ist aufzurufen std::ostream::flush(), dh Sie können sich vorstellen, dass seine Implementierung ungefähr so ​​aussieht:

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

Die std::ostream::flush()Funktion ruft technisch std::streambuf::pubsync()den Stream-Puffer (falls vorhanden) auf, der dem Stream zugeordnet ist: Der Stream-Puffer ist für das Puffern von Zeichen und das Senden von Zeichen an das externe Ziel verantwortlich, wenn der verwendete Puffer überlaufen würde oder wenn die interne Darstellung mit dem synchronisiert werden sollte externes Ziel, dh wann die Daten gelöscht werden sollen. Bei einem sequentiellen Stream bedeutet die Synchronisierung mit dem externen Ziel nur, dass alle gepufferten Zeichen sofort gesendet werden. Das heißt, using std::flushbewirkt , dass der Stream-Puffer seinen Ausgabepuffer leert. Wenn beispielsweise Daten in eine Konsolenbereinigung 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 zum Schreiben einer angemessenen Anzahl von Zeichen benötigt wird, ist jedoch im Wesentlichen identisch mit dem Schreiben von nur einem Zeichen. Die Anzahl der Zeichen hängt von vielen Merkmalen des Betriebssystems, der Dateisysteme usw. ab. Oft werden jedoch bis zu 4.000 Zeichen ungefähr zur gleichen Zeit wie nur ein Zeichen geschrieben. Das Puffern von Zeichen vor dem Senden mithilfe eines Puffers in Abhängigkeit von den Details des externen Ziels kann daher eine enorme Leistungsverbesserung darstellen.

Das Obige sollte zwei Ihrer drei Fragen beantworten. Die verbleibende Frage lautet: Wann würden Sie einen Stream spülen? Die Antwort lautet: Wann sollen die Zeichen an das externe Ziel geschrieben werden! Dies kann am Ende des Schreibens einer Datei (durch Schließen einer Datei wird der Puffer implizit geleert) oder unmittelbar vor der Anforderung von Benutzereingaben erfolgen (Hinweis, der std::coutbeim Lesen von std::cinas std::coutis std::istream::tie()'d to automatisch geleert wird std::cin). Obwohl es einige Fälle gibt, in denen Sie einen Stream explizit spülen möchten, finde ich sie ziemlich selten.

Schließlich habe ich versprochen, ein vollständiges Bild davon zu geben, was std::flushtatsächlich ist: Die Streams sind Klassenvorlagen, die mit verschiedenen Zeichentypen umgehen können (in der Praxis arbeiten sie mit charund wchar_t; es ist ziemlich kompliziert, sie mit anderen Zeichen arbeiten zu lassen, obwohl dies machbar ist, wenn Sie wirklich entschlossen sind ). Um std::flushmit allen Instanziierungen von Streams arbeiten zu können, handelt es sich zufällig um eine Funktionsvorlage mit einer Signatur wie der folgenden:

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

Bei std::flushsofortiger Verwendung mit einer Instanziierung spielt std::basic_ostreames keine Rolle: Der Compiler leitet die Vorlagenargumente automatisch ab. In Fällen, in denen diese Funktion nicht zusammen mit einer Erleichterung des Abzugs von Vorlagenargumenten erwähnt wird, kann der Compiler die Vorlagenargumente jedoch nicht ableiten.

Dietmar Kühl
quelle
3
Fantastische Antwort. Es konnten keine Qualitätsinformationen darüber gefunden werden, wie Flush irgendwo anders funktioniert hat.
Michael
31

Standardmäßig std::coutwird gepuffert und die tatsächliche Ausgabe wird erst gedruckt, wenn der Puffer voll ist oder eine andere Spülsituation 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.

Angenommen, Sie möchten einen Fortschrittsbericht melden, indem Sie einen einzelnen Punkt drucken:

for (;;)
{
    perform_expensive_operation();
    std::cout << '.';
    std::flush(std::cout);
}

Ohne die Spülung würden Sie die Ausgabe sehr lange nicht sehen.

Beachten Sie, dass std::endleine neue Zeile in einen Stream eingefügt wird und dieser gelöscht wird. Da das Spülen leicht teuer ist, std::endlsollte es nicht übermäßig verwendet werden, wenn das Spülen nicht ausdrücklich gewünscht wird.

Kerrek SB
quelle
7
Nur ein zusätzlicher Hinweis für Leser: coutIst nicht das einzige, was in C ++ gepuffert ist. ostreams werden im Allgemeinen standardmäßig gepuffert, was auch fstreams und dergleichen umfasst.
Cornstalks
Auch mit wird cindie Ausgabe ausgeführt, bevor sie gespült wird, nein?
Zaid Khan
21

Hier ist ein kurzes Programm, das Sie schreiben können, um zu beobachten, was Flush tut

#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 aus: Sie werden feststellen, dass es Zeile 1 druckt, pausiert und dann Zeile 2 und 3 druckt. Entfernen Sie nun den Flush-Aufruf und führen Sie das Programm erneut aus. Sie werden feststellen, dass das Programm pausiert und dann alle 3 Zeilen am druckt gleiche Zeit. Die erste Zeile wird gepuffert, bevor das Programm angehalten wird. Da der Puffer jedoch nie geleert wird, wird Zeile 1 erst beim endl-Aufruf von Zeile 2 ausgegeben.

Neil Anderson
quelle
Eine andere Möglichkeit, dies zu veranschaulichen, ist zu cout << "foo" << flush; std::abort();. Wenn Sie << flushauskommentieren / entfernen , gibt es KEINE AUSGABE! PS: Standard-Out-Debugging-DLLs, die aufgerufen werden, abortsind ein Albtraum. DLLs sollten niemals aufrufen abort.
Mark Storer
5

Ein Stream ist mit etwas verbunden. Bei der Standardausgabe kann es sich um die Konsole / den Bildschirm handeln oder um eine Pipe oder eine Datei. Zwischen Ihrem Programm und beispielsweise der Festplatte, auf der die Datei gespeichert ist, befindet sich viel Code. Zum Beispiel macht das Betriebssystem Sachen mit einer beliebigen Datei, oder das Festplattenlaufwerk selbst puffert möglicherweise Daten, um sie in Blöcken fester Größe schreiben zu können oder einfach nur effizienter zu sein.

Wenn Sie den Stream leeren, werden die Sprachbibliotheken, das Betriebssystem und die Hardware darüber informiert, dass alle Zeichen, die Sie bisher ausgegeben haben, bis zum Speicherplatz gezwungen werden sollen. Theoretisch könnte man nach einem "Flush" die Schnur aus der Wand werfen und diese Charaktere würden immer noch sicher aufbewahrt.

Ich sollte erwähnen, dass die Leute, die die OS-Treiber schreiben oder die Leute, die das Festplattenlaufwerk entwerfen, möglicherweise "Flush" als Vorschlag verwenden können und die Zeichen möglicherweise nicht wirklich ausschreiben. Selbst wenn die Ausgabe geschlossen ist, können sie eine Weile warten, um sie zu speichern. (Denken Sie daran, dass das Betriebssystem alle möglichen Dinge gleichzeitig erledigt und es möglicherweise effizienter ist, ein oder zwei Sekunden zu warten, um Ihre Bytes zu verarbeiten.)

Ein Flush ist also eine Art Checkpoint.

Ein weiteres Beispiel: Wenn die Ausgabe auf die Konsolenanzeige geht, wird durch eine Leerung sichergestellt, dass die Zeichen tatsächlich bis zu dem Punkt gelangen, an dem der Benutzer sie sehen kann. Dies ist wichtig, wenn Sie Tastatureingaben erwarten. Wenn Sie der Meinung sind, dass Sie eine Frage an die Konsole geschrieben haben und diese noch irgendwo in einem internen Puffer steckt, weiß der Benutzer nicht, was er als Antwort eingeben soll. Dies ist also ein Fall, in dem die Spülung wichtig ist.

Lee Meador
quelle