Qualcuno può spiegare (preferibilmente usando un inglese semplice) come std::flush
funziona?
- Che cos'è?
- Quando scaricheresti un flusso?
- Perché è importante?
Grazie.
Risposte:
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::flush
di 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::flush
con 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::flush
fa 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::cout
viene lavato automaticamente durante la lettura da std::cin
come 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::flush
realtà è: i flussi sono modelli di classe in grado di gestire diversi tipi di personaggi (in pratica lavorano con char
e wchar_t
; farli lavorare con altri personaggi è abbastanza complicato sebbene fattibile se sei davvero determinato ). Per essere in grado di utilizzare std::flush
con 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::flush
immediatamente con un'istanza di std::basic_ostream
esso 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.
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::endl
inserisce una nuova riga in un flusso oltre a provocarne lo scaricamento. Poiché il risciacquo è leggermente costoso, std::endl
non dovrebbe essere usato eccessivamente se il risciacquo non è espressamente desiderato.
cout
non è l'unica cosa memorizzata nel buffer in C ++. ostream
I messaggi in generale sono tipicamente bufferizzati per impostazione predefinita, che include anche se fstream
simili.
cin
esegue l'output prima di essere scaricato, no?
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.
cout << "foo" << flush; std::abort();
. Se commentate / rimuovete << flush
, NON ci sono OUTPUT! PS: le DLL di debug di uscita standard che chiamano abort
sono un incubo. Le DLL non dovrebbero mai chiamare abort
.
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.