¿Alguien puede explicar (preferiblemente en inglés sencillo) cómo std::flush
funciona?
- ¿Qué es?
- ¿Cuándo descargarías un arroyo?
- ¿Por qué es importante?
Gracias.
Dado que no se respondió lo que std::flush
sucede, aquí hay algunos detalles sobre lo que realmente es. std::flush
es un manipulador , es decir, una función con una firma específica. Para empezar simple, puede pensar std::flush
en tener la firma
std::ostream& std::flush(std::ostream&);
Sin embargo, la realidad es un poco más compleja (si está interesado, también se explica a continuación).
Los operadores de salida de sobrecarga de la clase de flujo toman operadores de esta forma, es decir, hay una función miembro que toma un manipulador como argumento. El operador de salida llama al manipulador con el objeto en sí:
std::ostream& std::ostream::operator<< (std::ostream& (*manip)(std::ostream&)) {
(*manip)(*this);
return *this;
}
Es decir, cuando "envía" std::flush
con a an std::ostream
, simplemente llama a la función correspondiente, es decir, las siguientes dos declaraciones son equivalentes:
std::cout << std::flush;
std::flush(std::cout);
Ahora, en std::flush()
sí mismo es bastante simple: todo lo que hace es llamar std::ostream::flush()
, es decir, puede imaginar su implementación para que se vea así:
std::ostream& std::flush(std::ostream& out) {
out.flush();
return out;
}
La std::ostream::flush()
función técnicamente llama std::streambuf::pubsync()
al búfer de flujo (si lo hay) que está asociado con el flujo: El búfer de flujo es responsable de almacenar caracteres en búfer y enviar caracteres al destino externo cuando el búfer utilizado se desbordaría o cuando la representación interna debería sincronizarse con el destino externo, es decir, cuando los datos se van a eliminar. En una secuencia secuencial, la sincronización con el destino externo solo significa que los caracteres almacenados en búfer se envían inmediatamente. Es decir, el uso std::flush
hace que el búfer de flujo vacíe su búfer de salida. Por ejemplo, cuando se escriben datos en una consola, el vaciado hace que los caracteres aparezcan en este punto de la consola.
Esto puede plantear la pregunta: ¿Por qué los personajes no se escriben de inmediato? La respuesta simple es que escribir caracteres suele ser bastante lento. Sin embargo, la cantidad de tiempo que se necesita para escribir una cantidad razonable de caracteres es esencialmente idéntica a escribir solo uno donde. La cantidad de caracteres depende de muchas características del sistema operativo, sistemas de archivos, etc., pero a menudo se escriben hasta 4k caracteres aproximadamente al mismo tiempo que un solo carácter. Por lo tanto, almacenar caracteres en búfer antes de enviarlos utilizando un búfer en función de los detalles del destino externo puede ser una gran mejora del rendimiento.
Lo anterior debería responder a dos de sus tres preguntas. La pregunta restante es: ¿Cuándo descargarías un arroyo? La respuesta es: ¡cuando los caracteres deben escribirse en el destino externo! Esto puede estar en el extremo de escribir un archivo (cerrar un archivo implícitamente vacía el búfer, sin embargo) o inmediatamente antes de pedir la entrada del usuario (nota que std::cout
se vacía automáticamente cuando se lee de std::cin
como std::cout
es std::istream::tie()
'd a std::cin
). Aunque puede haber algunas ocasiones en las que desee explícitamente descargar un flujo, las encuentro bastante raras.
Finalmente, prometí dar una imagen completa de lo que std::flush
realmente es: Las transmisiones son plantillas de clases capaces de lidiar con diferentes tipos de personajes (en la práctica, funcionan con char
y wchar_t
; hacer que funcionen con otros personajes es bastante complicado, aunque factible si estás realmente decidido ). Para poder usarlo std::flush
con todas las instancias de flujos, resulta ser una plantilla de función con una firma como esta:
template <typename cT, typename Traits>
std::basic_ostream<cT, Traits>& std::flush(std::basic_ostream<cT, Traits>&);
Cuando se usa std::flush
inmediatamente con una instanciación de std::basic_ostream
esto, realmente no importa: el compilador deduce los argumentos de la plantilla automáticamente. Sin embargo, en los casos en los que esta función no se menciona junto con algo que facilite la deducción del argumento de la plantilla, el compilador no podrá deducir los argumentos de la plantilla.
De forma predeterminada,
std::cout
está almacenado en búfer y la salida real solo se imprime una vez que el búfer está lleno o se produce alguna otra situación de descarga (por ejemplo, una nueva línea en la secuencia). A veces, desea asegurarse de que la impresión se realice de inmediato y debe limpiar manualmente.Por ejemplo, suponga que desea informar un informe de progreso imprimiendo un solo punto:
for (;;) { perform_expensive_operation(); std::cout << '.'; std::flush(std::cout); }
Sin el lavado, no vería la salida durante mucho tiempo.
Tenga en cuenta que
std::endl
inserta una nueva línea en una secuencia y también hace que se vacíe. Dado que el enjuague es un poco caro,std::endl
no debe usarse en exceso si no se desea expresamente.fuente
cout
no es lo único que se almacena en búfer en C ++.ostream
Los s en general suelen almacenarse en búfer de forma predeterminada, lo que también incluyefstream
s y similares.cin
realiza la salida antes de que se vacíe, ¿no?Aquí hay un programa corto que puede escribir para observar qué está haciendo 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; }
Ejecute este programa: notará que imprime la línea 1, hace una pausa, luego imprime las líneas 2 y 3. Ahora elimine la llamada de descarga y ejecute el programa nuevamente; notará que el programa se detiene y luego imprime las 3 líneas en el Mismo tiempo. La primera línea se almacena en búfer antes de que el programa haga una pausa, pero debido a que el búfer nunca se vacía, la línea 1 no se envía hasta la llamada endl de la línea 2.
fuente
cout << "foo" << flush; std::abort();
. Si comenta o elimina<< flush
, ¡NO HAY SALIDA! PD: DLL de depuración estándar que llamanabort
es una pesadilla. Las DLL nunca deberían llamarabort
.Una corriente está conectada a algo. En el caso de la salida estándar, podría ser la consola / pantalla o podría redirigirse a una tubería o un archivo. Hay mucho código entre su programa y, por ejemplo, el disco duro donde se almacena el archivo. Por ejemplo, el sistema operativo está haciendo cosas con cualquier archivo o la propia unidad de disco podría estar almacenando datos en búfer para poder escribirlos en bloques de tamaño fijo o simplemente para ser más eficiente.
Cuando descarga la secuencia, le dice a las bibliotecas de idiomas, el sistema operativo y el hardware que desea que cualquier carácter que haya generado hasta ahora sea forzado hasta el almacenamiento. Teóricamente, después de una 'descarga', podría patear el cable de la pared y esos personajes aún se almacenarían de manera segura.
Debo mencionar que las personas que escriben los controladores del sistema operativo o las personas que diseñan la unidad de disco pueden ser libres de usar 'flush' como sugerencia y es posible que realmente no escriban los caracteres. Incluso cuando la salida está cerrada, es posible que esperen un poco para guardarlos. (Recuerde que el sistema operativo hace todo tipo de cosas a la vez y podría ser más eficiente esperar uno o dos segundos para manejar sus bytes).
Entonces, una descarga es una especie de punto de control.
Un ejemplo más: si la salida va a la pantalla de la consola, una descarga se asegurará de que los personajes lleguen hasta donde el usuario pueda verlos. Esto es algo importante que debe hacer cuando espera una entrada de teclado. Si cree que ha escrito una pregunta en la consola y todavía está atascada en algún búfer interno en algún lugar, el usuario no sabe qué escribir la respuesta. Entonces, este es un caso en el que la descarga es importante.
fuente