Как работает std :: flush?

85

Может кто-нибудь объяснить (желательно на простом английском), как std::flushработает?

  • Что это такое?
  • Когда бы вы промыли ручей?
  • Почему это важно?

Спасибо.

Дасару
источник

Ответы:

139

Поскольку не было ответа, что 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::cinas std::coutis 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это не имеет особого значения: компилятор автоматически выводит аргументы шаблона. Однако в случаях, когда эта функция не упоминается вместе с чем-то, что упрощает вывод аргументов шаблона, компилятор не сможет вывести аргументы шаблона.

Дитмар Кюль
источник
3
Фантастический ответ. Не удалось найти качественной информации о том, как работает промывка, где-либо еще.
Майкл
32

По умолчанию std::coutбуферизируется, и фактический вывод печатается только после заполнения буфера или возникновения другой ситуации очистки (например, новой строки в потоке). Иногда хочется убедиться, что печать происходит сразу, и промывать нужно вручную.

Например, предположим, что вы хотите сообщить об отчете о проделанной работе, напечатав одну точку:

for (;;)
{
    perform_expensive_operation();
    std::cout << '.';
    std::flush(std::cout);
}

Без промывки вы бы не увидели результат очень долго.

Обратите внимание, что std::endlвставляет новую строку в поток, а также заставляет его сбрасывать. Поскольку промывка является умеренно дорогой, std::endlне следует использовать ее чрезмерно, если промывка явно не требуется.

Керрек С.Б.
источник
7
Просто дополнительное примечание для читателей: coutэто не единственное, что буферизуется в C ++. ostreams обычно по умолчанию буферизуются, что также включает fstreams и т.п.
Cornstalks
Также использование cinвыполняет вывод до того, как он будет сброшен, нет?
Зайд Хан,
21

Вот небольшая программа, которую вы можете написать, чтобы наблюдать за тем, что делает флеш

#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.
Марк Сторер
5

Поток к чему-то подключен. В случае стандартного вывода это может быть консоль / экран, или он может быть перенаправлен в канал или файл. Между вашей программой и, например, жестким диском, на котором хранится файл, находится много кода. Например, операционная система выполняет какие-то действия с любым файлом, или сам диск может буферизовать данные, чтобы иметь возможность записывать их блоками фиксированного размера или просто для большей эффективности.

Когда вы сбрасываете поток, он сообщает языковым библиотекам, операционной системе и оборудованию, что вы хотите, чтобы любые символы, которые вы уже вывели, будут принудительно перемещены в хранилище. Теоретически, после «промывки», вы можете выбросить шнур из стены, и эти символы все равно будут безопасно храниться.

Я должен упомянуть, что люди, пишущие драйверы для ОС или разработчики дисковода, могут свободно использовать «сброс» в качестве предложения, и они могут не выписывать символы. Даже когда выход закрыт, они могут подождать некоторое время, чтобы сохранить их. (Помните, что операционная система выполняет все виды задач одновременно, и может быть более эффективным подождать секунду или две, чтобы обработать ваши байты.)

Так что флеш - это своего рода контрольная точка.

Еще один пример: если вывод выводится на консоль, сброс гарантирует, что символы действительно дошли до того места, где их может видеть пользователь. Это важно, когда вы ожидаете ввода с клавиатуры. Если вы думаете, что написали вопрос на консоли, но он все еще застрял где-то во внутреннем буфере, пользователь не знает, что вводить в ответ. Итак, это тот случай, когда важен смыв.

Ли Мидор
источник