Хтось може пояснити (бажано, використовуючи звичайну англійську), як це 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
допомогою 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
це не має значення: компілятор автоматично виводить аргументи шаблону. Однак у випадках, коли ця функція не згадується разом із чимось, що полегшує виведення аргументу шаблону, компілятор не зможе вивести аргументи шаблону.
За замовчуванням 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
.
Потік пов’язаний з чимось. У випадку стандартного виводу це може бути консоль / екран або його можна перенаправити в конвеєр або файл. Між вашою програмою та, наприклад, жорстким диском, на якому зберігається файл, багато коду. Наприклад, операційна система робить речі з будь-якими файлами, або сам дисковод може буферизувати дані, щоб мати можливість записати їх у блоки фіксованого розміру або просто бути більш ефективними.
Коли ви очищаєте потік, він повідомляє мовні бібліотеки, операційну систему та апаратне забезпечення, що ви хочете, щоб будь-які символи, які ви виводили до цього часу, були примушені повністю зберігати. Теоретично, після "змиву", ви можете вибити шнур зі стіни, і ці персонажі все одно надійно зберігатимуться.
Я повинен сказати, що люди, які пишуть драйвери ОС або люди, що розробляють дисковод, можуть вільно використовувати "змивання" як пропозицію, і вони не можуть виписувати символи. Навіть коли вихід закритий, вони можуть зачекати деякий час, щоб зберегти їх. (Пам'ятайте, що операційна система виконує всілякі дії одночасно, і може бути ефективніше зачекати секунду-дві, щоб обробити ваші байти.)
Отже, флеш - це свого роду контрольний пункт.
Ще один приклад: якщо вихід виводиться на дисплей консолі, флеш переконує, що символи насправді потрапляють до місця, де користувач може їх бачити. Це важливо зробити, коли ви очікуєте введення з клавіатури. Якщо ви вважаєте, що написали запитання на консоль і воно все ще застрягло у якомусь внутрішньому буфері, користувач не знає, що вводити у відповідь. Отже, це той випадок, коли флеш важливий.