Come funziona std :: flush work?

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

  • Che cos’è?
  • Quando svuotare un stream?
  • Perché è importante?

Grazie.

Dal momento che non è stata data risposta a cosa 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 a std::flush di avere la firma

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

La realtà è un po ‘più complessa, anche se (se sei interessato, è spiegato anche in seguito).

Gli operatori di output di sovraccarico della class stream assumono operatori di questo modulo, ovvero c’è una funzione membro che accetta un manipolatore come argomento. L’operatore di output chiama il manipolatore con l’object stesso:

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

Cioè, quando "uscite" std::flush con uno std::ostream , chiama semplicemente la funzione corrispondente, cioè le seguenti due istruzioni sono equivalenti:

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

Ora, std::flush() stesso è abbastanza semplice: tutto ciò che fa è chiamare std::ostream::flush() , cioè, puoi immaginare la sua implementazione per assomigliare a qualcosa del genere:

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

La funzione std::ostream::flush() chiama tecnicamente std::streambuf::pubsync() sul buffer del stream (se presente) associato al stream: il buffer del stream è responsabile del buffering dei caratteri e dell'invio di caratteri al destinazione esterna quando il buffer utilizzato si riempie eccessivamente o quando la rappresentazione interna deve essere sincronizzata con la destinazione esterna, cioè quando i dati devono essere svuotati. In un stream sequenziale la sincronizzazione con la destinazione esterna significa semplicemente che tutti i caratteri memorizzati nel buffer vengono inviati immediatamente. Cioè, usando std::flush fa in modo che il buffer del stream svuoti il ​​suo buffer di output. Ad esempio, quando i dati vengono scritti su un flush della console, i caratteri vengono visualizzati a questo punto sulla console.

Questo potrebbe sollevare la domanda: perché i personaggi non vengono scritti immediatamente? La semplice risposta è che scrivere caratteri è 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 fino a qualcosa come 4k caratteri sono scritti all'incirca nello stesso momento di un solo carattere. Pertanto, il buffering dei caratteri prima di inviarli utilizzando un buffer in base ai dettagli della destinazione esterna può rappresentare un enorme miglioramento delle prestazioni.

Quanto sopra dovrebbe rispondere a due delle vostre tre domande. La domanda rimanente è: quando svuotare un stream? La risposta è: quando i personaggi dovrebbero essere scritti nella destinazione esterna! Questo potrebbe essere alla fine della scrittura di un file (chiudendo un file in modo implicito il buffer, comunque) o immediatamente prima di chiedere l'input dell'utente (nota che std::cout viene automaticamente svuotato durante la lettura da std::cin come std::cout è std::istream::tie() 'd to std::cin ). Anche se ci possono essere alcune occasioni in cui desideri esplicitamente svuotare un stream, trovo che siano piuttosto rari.

Infine, ho promesso di dare un quadro completo di ciò che std::flush realtà è: i flussi sono modelli di classi in grado di gestire diversi tipi di caratteri (in pratica funzionano con char e wchar_t , farli lavorare con altri personaggi è abbastanza complicato anche se fattibile se sei veramente determinato). Per essere in grado di usare std::flush con tutte le istanze degli stream, sembra essere un modello di funzione con una firma come questa:

 template  std::basic_ostream& std::flush(std::basic_ostream&); 

Quando si usa std::flush immediatamente con un'istanza di std::basic_ostream non ha molta importanza: il compilatore deduce automaticamente gli argomenti del template. Tuttavia, nei casi in cui questa funzione non è menzionata insieme a qualcosa che facilita la deduzione degli argomenti del template, il compilatore non riuscirà a dedurre gli argomenti del template.

Per impostazione predefinita, std::cout viene memorizzato nel buffer e l’output effettivo viene stampato solo quando il buffer è pieno o si verificano altre situazioni di flushing (ad esempio una nuova riga nel stream). A volte vuoi essere sicuro che la stampa avvenga immediatamente, e devi svuotare 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 lo svuotamento, non vedresti l'output per un tempo molto lungo.

Nota che std::endl inserisce una newline in uno stream e lo fa svuotare. Poiché il lavaggio è moderatamente costoso, std::endl non dovrebbe essere usato eccessivamente se il lavaggio non è espressamente desiderato.

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

 #include  #include  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 linea 1, fa una pausa, quindi stampa le linee 2 e 3. Ora rimuovi la chiamata a filo ed esegui di nuovo il programma: noterai che il programma fa una pausa e poi stampa tutte e 3 le righe sul 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 stream è connesso a qualcosa. Nel caso dell’output standard, potrebbe essere la console / schermo o potrebbe essere reindirizzato a una pipe oa un file. C’è molto codice tra il tuo programma e, ad esempio, il disco rigido in cui è archiviato il file. Ad esempio, il sistema operativo sta facendo cose con qualsiasi file o il disco stesso potrebbe essere il buffering dei dati per poterlo scrivere in blocchi di dimensioni fisse o semplicemente per essere più efficiente.

Quando svuoti il ​​stream, indica alle librerie delle lingue, al sistema operativo e all’hardware che desideri i caratteri che hai finora inviato per essere forzato fino all’archiviazione. Teoricamente, dopo un “flush”, potresti calciare il cavo fuori dal muro e quei personaggi verrebbero comunque archiviati in modo sicuro.

Dovrei menzionare che le persone che scrivono i driver os o le persone che progettano l’unità disco potrebbero essere liberi di usare ‘flush’ come suggerimento e potrebbero non scrivere i caratteri. Anche quando l’output è chiuso, potrebbero attendere qualche istante per salvarli. (Ricorda che l’os fa tutti i tipi di cose contemporaneamente e potrebbe essere più efficiente aspettare un secondo o due per gestire i byte.)

Quindi un colore è una sorta di checkpoint.

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