Может ли кто-нибудь объяснить (желательно используя простой английский), как работает std::flush
?
- Что это такое?
- Когда вы промоете поток?
- Почему это важно?
Спасибо.
Может ли кто-нибудь объяснить (желательно используя простой английский), как работает std::flush
?
Спасибо.
Так как на него не ответил, что std::flush
, вот некоторые подробности о том, что это на самом деле. std::flush
является манипулятором, то есть функцией со специальной сигнатурой. Чтобы начать с простого, вы можете думать о std::flush
наличия подписи
std::ostream& std::flush(std::ostream&);
Реальность немного сложнее, хотя (если вам интересно, это объясняется ниже).
Операторы перегрузки класса потока перехватывают операторы этой формы, т.е. существует функция-член, принимающая манипулятор в качестве аргумента. Оператор вывода вызывает манипулятор с самим объектом:
std::ostream& std::ostream::operator<< (std::ostream& (*manip)(std::ostream&)) {
(*manip)(*this);
return *this;
}
То есть, когда вы "выводите" std::flush
с помощью std::ostream
, он просто вызывает соответствующую функцию, т.е. следующие два утверждения эквивалентны:
std::cout << std::flush;
std::flush(std::cout);
Теперь std::flush()
сам по себе довольно прост: все, что он делает, это вызвать std::ostream::flush()
, то есть вы можете представить себе его реализацию, чтобы выглядеть примерно так:
std::ostream& std::flush(std::ostream& out) {
out.flush();
return out;
}
Функция std::ostream::flush()
технически вызывает std::streambuf::pubsync()
в буфере потока (если есть), который связан с потоком: буфер потока отвечает за буферизацию символов и отправку символов во внешний адрес, когда использованный буфер переполняется или когда внутреннее представление должно синхронизироваться с внешним получателем, т.е. когда данные должны быть сброшены. Синхронизация последовательного потока с внешним адресатом означает, что любые буферизованные символы немедленно отправляются. То есть использование std::flush
заставляет буфер потока очищать свой выходной буфер. Например, когда данные записываются в консольную промывку, символы появляются в этой точке на консоли.
Это может поставить вопрос: почему персонажи сразу не написаны? Простой ответ заключается в том, что писать символы, как правило, довольно медленно. Однако время, необходимое для написания разумного количества символов, по существу идентично написанию только одного. Количество символов зависит от многих характеристик операционной системы, файловых систем и т.д., Но часто до чего-то вроде 4k символов написано примерно в то же время, что и один символ. Таким образом, буферизация символов перед отправкой их с использованием буфера в зависимости от деталей внешнего адресата может быть огромным улучшением производительности.
Вышеупомянутое должно ответить на два из ваших трех вопросов. Остается вопрос: когда вы промоете поток? Ответ: Когда символы должны быть записаны во внешнее место назначения! Это может быть в конце написания файла (закрытие файла неявно очищает буфер) или непосредственно перед запросом ввода пользователя (обратите внимание, что std::cout
автоматически сбрасывается при чтении из std::cin
, поскольку std::cout
есть std::istream::tie()
'd до std::cin
). Хотя может быть несколько случаев, когда вы явно хотите очистить поток, я считаю их довольно редкими.
Наконец, я обещал дать полное представление о том, что на самом деле есть std::flush
: потоки - это шаблоны классов, способные работать с разными типами символов (на практике они работают с char
и wchar_t
; другие персонажи довольно вовлечены, хотя это возможно, если вы действительно настроены). Чтобы иметь возможность использовать std::flush
со всеми экземплярами потоков, это, как правило, шаблон функции с такой сигнатурой:
template <typename cT, typename Traits>
std::basic_ostream<cT, Traits>& std::flush(std::basic_ostream<cT, Traits>&);
При непосредственном использовании std::flush
с экземпляром std::basic_ostream
это не имеет значения: компилятор автоматически выводит аргументы шаблона. Однако в тех случаях, когда эта функция не упоминается вместе с чем-то, облегчающим вывод аргумента шаблона, компилятор не сможет вывести аргументы шаблона.
По умолчанию std::cout
буферизуется, и фактический вывод печатается только после заполнения буфера или возникновения какой-либо другой ситуации с промывкой (например, новой строки в потоке). Иногда вы хотите убедиться, что печать происходит немедленно, и вам нужно выполнить ручную ручную установку.
Например, предположим, что вы хотите сообщить отчет о ходе печати, напечатав одну точку:
for (;;)
{
perform_expensive_operation();
std::cout << '.';
std::flush(std::cout);
}
Без промывки вы не увидите вывод в течение очень долгого времени.
Обратите внимание, что std::endl
вставляет новую строку в поток, а также вызывает его сброс. Поскольку промывка незначительно дорогая, std::endl
не следует использовать чрезмерно, если промывка явно не требуется.
Вот короткая программа, которую вы можете написать, чтобы наблюдать за тем, что делает флеш
#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;
}
Запустите эту программу: вы заметите, что она печатает строку 1, приостанавливает, затем печатает строки 2 и 3. Теперь удалите флеш-вызов и запустите программу еще раз - вы заметите, что программа приостанавливается, а затем печатает все 3 линий в одно и то же время. Первая строка буферизуется до того, как программа приостанавливается, но поскольку буфер никогда не сбрасывается, строка 1 не выводится до вызова endl из строки 2.
Поток связан с чем-то. В случае стандартного вывода это может быть консоль/экран, или он может быть перенаправлен на канал или файл. Существует много кода между вашей программой и, например, жесткий диск, на котором хранится файл. Например, операционная система делает материал с любым файлом или сам диск может быть буферизующим данными, чтобы иметь возможность записывать его в блоки фиксированного размера или просто быть более эффективным.
Когда вы очищаете поток, он сообщает языковым библиотекам, os и аппаратным средствам, что вы хотите, чтобы какие-либо символы, которые у вас есть, до сих пор должны быть полностью загружены. Теоретически, после "флеша" вы можете вытащить шнур из стены, и эти символы будут сохранены.
Я должен упомянуть, что люди, пишущие драйверы os или люди, создающие дисковод, могут свободно использовать "флеш" в качестве предложения, и они могут не писать персонажей. Даже когда выход закрыт, они могут подождать некоторое время, чтобы сохранить их. (Помните, что os делает всевозможные вещи одновременно, и может быть более эффективным ждать второй или две, чтобы обрабатывать ваши байты.)
Итак, флеш - это своего рода контрольная точка.
Еще один пример: если вывод будет отображаться на дисплее консоли, флеш будет следить за тем, чтобы персонажи добирались до того места, где пользователь может их видеть. Это важно, когда вы ожидаете ввода на клавиатуре. Если вы считаете, что вы написали вопрос на консоль и все еще застряли в каком-то внутреннем буфере где-то, пользователь не знает, что ввести в ответ. Итак, это тот случай, когда флеш важен.