Come funziona std :: flush?


85

Qualcuno può spiegare (preferibilmente usando un inglese semplice) come std::flushfunziona?

  • Che cos'è?
  • Quando scaricheresti un flusso?
  • Perché è importante?

Grazie.

Risposte:


140

Dal momento che non è stato risposto a ciò che std::flush accade, ecco alcuni dettagli su cosa sia effettivamente. std::flushè un manipolatore , cioè una funzione con una firma specifica. Per iniziare semplice, puoi pensare std::flushdi avere la firma

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

La realtà è un po 'più complessa, però (se sei interessato, viene spiegato anche di seguito).

Gli operatori di output di sovraccarico della classe stream che accettano operatori di questa forma, cioè, c'è una funzione membro che prende un manipolatore come argomento. L'operatore di output chiama il manipolatore con l'oggetto stesso:

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

Cioè, quando "esegui l'output" std::flushcon to an std::ostream, chiama solo la funzione corrispondente, cioè le due istruzioni seguenti sono equivalenti:

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

Ora, di per std::flush()sé è abbastanza semplice: tutto ciò che fa è chiamare std::ostream::flush(), cioè puoi immaginare la sua implementazione in modo simile a questo:

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

La std::ostream::flush()funzione chiama tecnicamente std::streambuf::pubsync()il buffer del flusso (se presente) che è associato al flusso: Il buffer del flusso è responsabile del buffering dei caratteri e dell'invio di caratteri alla destinazione esterna quando il buffer utilizzato andrebbe in overflow o quando la rappresentazione interna dovrebbe essere sincronizzata con il destinazione esterna, ovvero quando i dati devono essere scaricati. In un flusso sequenziale la sincronizzazione con la destinazione esterna significa semplicemente che tutti i caratteri memorizzati nel buffer vengono inviati immediatamente. Cioè, usando std::flushfa sì che il buffer del flusso svuoti il ​​suo buffer di output. Ad esempio, quando i dati vengono scritti su una console, lo scaricamento fa sì che i caratteri vengano visualizzati a questo punto sulla console.

Ciò potrebbe sollevare la domanda: perché i caratteri non vengono scritti immediatamente? La semplice risposta è che scrivere i personaggi è generalmente piuttosto lento. Tuttavia, la quantità di tempo necessaria per scrivere una quantità ragionevole di caratteri è essenzialmente identica alla scrittura di uno solo dove. La quantità di caratteri dipende da molte caratteristiche del sistema operativo, dei file system, ecc., Ma spesso i caratteri fino a 4k vengono scritti all'incirca nello stesso tempo di un solo carattere. Pertanto, il buffering dei caratteri prima di inviarli utilizzando un buffer a seconda dei dettagli della destinazione esterna può essere un enorme miglioramento delle prestazioni.

Quanto sopra dovrebbe rispondere a due delle tue tre domande. La domanda rimanente è: quando scaricheresti un flusso? La risposta è: quando i caratteri devono essere scritti nella destinazione esterna! Questo può essere alla fine di scrivere un file (chiusura di un file implicitamente vampate buffer, però) o immediatamente prima di richiedere l'input dell'utente (nota che std::coutviene lavato automaticamente durante la lettura da std::cincome std::coutè std::istream::tie()'d a std::cin). Sebbene possano esserci alcune occasioni in cui desideri esplicitamente scaricare un flusso, trovo che siano piuttosto rare.

Infine, ho promesso di dare un quadro completo di ciò che in std::flushrealtà è: i flussi sono modelli di classe in grado di gestire diversi tipi di personaggi (in pratica lavorano con chare wchar_t; farli lavorare con altri personaggi è abbastanza complicato sebbene fattibile se sei davvero determinato ). Per essere in grado di utilizzare std::flushcon tutte le istanze di flussi, sembra essere un modello di funzione con una firma come questa:

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

Quando si utilizza std::flushimmediatamente con un'istanza di std::basic_ostreamesso non ha molta importanza: il compilatore deduce automaticamente gli argomenti del modello. Tuttavia, nei casi in cui questa funzione non è menzionata insieme a qualcosa che faciliti la deduzione dell'argomento del modello, il compilatore non riuscirà a dedurre gli argomenti del modello.


3
Risposta fantastica. Impossibile trovare informazioni di qualità su come funzionava lo scarico altrove.
Michael,

32

Per impostazione predefinita, std::coutè bufferizzato e l'output effettivo viene stampato solo quando il buffer è pieno o si verifica qualche altra situazione di svuotamento (ad esempio una nuova riga nel flusso). A volte vuoi assicurarti che la stampa avvenga immediatamente e devi lavare manualmente.

Ad esempio, supponi di voler segnalare un rapporto sullo stato di avanzamento stampando un singolo punto:

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

Senza il lavaggio, non vedresti l'output per molto tempo.

Notare che std::endlinserisce una nuova riga in un flusso oltre a provocarne lo scaricamento. Poiché il risciacquo è leggermente costoso, std::endlnon dovrebbe essere usato eccessivamente se il risciacquo non è espressamente desiderato.


7
Solo una nota aggiuntiva per i lettori: coutnon è l'unica cosa memorizzata nel buffer in C ++. ostreamI messaggi in generale sono tipicamente bufferizzati per impostazione predefinita, che include anche se fstreamsimili.
Steli di mais

Anche usando cinesegue l'output prima di essere scaricato, no?
Zaid Khan

21

Ecco un breve programma che puoi scrivere per osservare cosa sta facendo flush

#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;
}

Esegui questo programma: noterai che stampa la riga 1, fa una pausa, quindi stampa la riga 2 e 3. Ora rimuovi la chiamata flush ed esegui di nuovo il programma: noterai che il programma si ferma e poi stampa tutte e 3 le righe contemporaneamente. La prima riga viene memorizzata nel buffer prima che il programma si interrompa, ma poiché il buffer non viene mai scaricato, la riga 1 non viene emessa fino alla chiamata endl dalla riga 2.


Un altro modo per illustrare questo è cout << "foo" << flush; std::abort();. Se commentate / rimuovete << flush, NON ci sono OUTPUT! PS: le DLL di debug di uscita standard che chiamano abortsono un incubo. Le DLL non dovrebbero mai chiamare abort.
Mark Storer

5

Un flusso è connesso a qualcosa. Nel caso dell'output standard, potrebbe essere la console / schermo o potrebbe essere reindirizzato a una pipe o un file. C'è molto codice tra il programma e, ad esempio, il disco rigido in cui è archiviato il file. Ad esempio, il sistema operativo sta facendo cose con qualsiasi file o l'unità disco stessa potrebbe eseguire il buffering dei dati per poterli scrivere in blocchi di dimensioni fisse o semplicemente per essere più efficiente.

Quando si scarica il flusso, viene comunicato alle librerie di lingue, al sistema operativo e all'hardware che si desidera che tutti i caratteri di cui è stato eseguito l'output fino ad ora vengano forzati fino alla memorizzazione. Teoricamente, dopo un "lavaggio", potresti calciare il cavo fuori dal muro e quei personaggi sarebbero ancora conservati al sicuro.

Devo menzionare che le persone che scrivono i driver del sistema operativo o le persone che progettano il disco rigido potrebbero essere libere di usare "flush" come suggerimento e potrebbero non scrivere i caratteri. Anche quando l'output è chiuso, potrebbero attendere un po 'per salvarli. (Ricorda che il sistema operativo fa ogni genere di cose contemporaneamente e potrebbe essere più efficiente aspettare un secondo o due per gestire i tuoi byte.)

Quindi un flush è una sorta di checkpoint.

Un altro esempio: se l'output va al display della console, un flush assicurerà che i caratteri arrivino effettivamente fino a dove l'utente può vederli. Questa è una cosa importante da fare quando ti aspetti un input dalla tastiera. Se pensi di aver scritto una domanda alla console e che sia ancora bloccata da qualche parte in un buffer interno, l'utente non sa cosa digitare per rispondere. Quindi, questo è un caso in cui il colore è importante.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.