Як працює std :: flush?


84

Хтось може пояснити (бажано, використовуючи звичайну англійську), як це std::flushпрацює?

  • Що це?
  • Коли б ви промивали потік?
  • Чому це важливо?

Дякую.

Відповіді:


137

Оскільки на це не було відповіді, що 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допомогою an 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призводить до того, що буфер потоку змиває свій вихідний буфер. Наприклад, коли дані записуються на консоль, змивання змушує символи з’являтися в цей момент на консолі.

Це може постати питанням: чому символи не пишуться відразу? Проста відповідь полягає в тому, що написання символів, як правило, відбувається досить повільно. Однак кількість часу, необхідного для написання розумної кількості символів, по суті ідентична написанню лише того, де. Кількість символів залежить від багатьох характеристик операційної системи, файлових систем тощо, але часто приблизно до 4 тис. Символів записується приблизно в той самий час, що і один символ. Таким чином, буферизація символів перед відправкою їх за допомогою буфера залежно від деталей зовнішнього призначення може суттєво покращити продуктивність.

Вищевикладене має відповісти на два з трьох ваших запитань. Залишилося запитання: коли б ви промивали потік? Відповідь така: Коли символи слід писати у зовнішній пункт призначення! Це може бути в кінці запису файлу (закриття файлу неявно переповнити буфер, хоча) або безпосередньо перед задаєте користувача (зверніть увагу , що 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це не має значення: компілятор автоматично виводить аргументи шаблону. Однак у випадках, коли ця функція не згадується разом із чимось, що полегшує виведення аргументу шаблону, компілятор не зможе вивести аргументи шаблону.


3
Фантастична відповідь. Не вдалося знайти якісну інформацію про те, як змив працював деінде.
Michael

31

За замовчуванням std::coutбуферизується, а фактичний вихід виводиться лише після того, як буфер заповнюється або виникає якась інша ситуація змиву (наприклад, новий рядок у потоці). Іноді потрібно переконатися, що друк відбувається негайно, і потрібно промити вручну.

Наприклад, припустимо, ви хочете повідомити про звіт про хід, надрукувавши одну крапку:

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

Без змиву ви не бачили б вихідних даних дуже довго.

Зверніть увагу, що std::endlвставляє новий рядок у потік, а також змушує його змиватися. Оскільки промивання є досить дорогим, std::endlне слід використовувати надмірно, якщо промивання не є явно бажаним.


7
Просто додаткова примітка для читачів: coutчи не єдине, що буферизується на C ++. ostreamЯк правило, s зазвичай буферизуються за замовчуванням, що також включає 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.
Mark Storer

5

Потік пов’язаний з чимось. У випадку стандартного виводу це може бути консоль / екран або його можна перенаправити в конвеєр або файл. Між вашою програмою та, наприклад, жорстким диском, на якому зберігається файл, багато коду. Наприклад, операційна система робить речі з будь-якими файлами, або сам дисковод може буферизувати дані, щоб мати можливість записати їх у блоки фіксованого розміру або просто бути більш ефективними.

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

Я повинен сказати, що люди, які пишуть драйвери ОС або люди, що розробляють дисковод, можуть вільно використовувати "змивання" як пропозицію, і вони не можуть виписувати символи. Навіть коли вихід закритий, вони можуть зачекати деякий час, щоб зберегти їх. (Пам'ятайте, що операційна система виконує всілякі дії одночасно, і може бути ефективніше зачекати секунду-дві, щоб обробити ваші байти.)

Отже, флеш - це свого роду контрольний пункт.

Ще один приклад: якщо вихід виводиться на дисплей консолі, флеш переконує, що символи насправді потрапляють до місця, де користувач може їх бачити. Це важливо зробити, коли ви очікуєте введення з клавіатури. Якщо ви вважаєте, що написали запитання на консоль і воно все ще застрягло у якомусь внутрішньому буфері, користувач не знає, що вводити у відповідь. Отже, це той випадок, коли флеш важливий.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.