Может кто-нибудь объяснить (желательно на простом английском), как 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
as std::cout
is std::istream::tie()
'd to 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
не следует использовать ее чрезмерно, если промывка явно не требуется.
cout
это не единственное, что буферизуется в C ++. ostream
s обычно по умолчанию буферизуются, что также включает fstream
s и т.п.
cin
выполняет вывод до того, как он будет сброшен, нет?
Вот небольшая программа, которую вы можете написать, чтобы наблюдать за тем, что делает флеш
#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.
cout << "foo" << flush; std::abort();
. Если закомментировать / удалить << flush
, ВЫХОДА НЕТ! PS: стандартные библиотеки DLL, которые вызывают отладку, abort
- это кошмар. DLL никогда не должны вызывать abort
.
Поток к чему-то подключен. В случае стандартного вывода это может быть консоль / экран, или он может быть перенаправлен в канал или файл. Между вашей программой и, например, жестким диском, на котором хранится файл, находится много кода. Например, операционная система выполняет какие-то действия с любым файлом, или сам диск может буферизовать данные, чтобы иметь возможность записывать их блоками фиксированного размера или просто для большей эффективности.
Когда вы сбрасываете поток, он сообщает языковым библиотекам, операционной системе и оборудованию, что вы хотите, чтобы любые символы, которые вы уже вывели, будут принудительно перемещены в хранилище. Теоретически, после «промывки», вы можете выбросить шнур из стены, и эти символы все равно будут безопасно храниться.
Я должен упомянуть, что люди, пишущие драйверы для ОС или разработчики дисковода, могут свободно использовать «сброс» в качестве предложения, и они могут не выписывать символы. Даже когда выход закрыт, они могут подождать некоторое время, чтобы сохранить их. (Помните, что операционная система выполняет все виды задач одновременно, и может быть более эффективным подождать секунду или две, чтобы обработать ваши байты.)
Так что флеш - это своего рода контрольная точка.
Еще один пример: если вывод выводится на консоль, сброс гарантирует, что символы действительно дошли до того места, где их может видеть пользователь. Это важно, когда вы ожидаете ввода с клавиатуры. Если вы думаете, что написали вопрос на консоли, но он все еще застрял где-то во внутреннем буфере, пользователь не знает, что вводить в ответ. Итак, это тот случай, когда важен смыв.