Jak działa std :: flush?

85

Czy ktoś może wyjaśnić (najlepiej używając zwykłego angielskiego), jak std::flushdziała?

  • Co to jest?
  • Kiedy spłukiwałbyś strumień?
  • Dlaczego to jest ważne?

Dziękuję Ci.

Dasaru
źródło

Odpowiedzi:

140

Ponieważ nie udzielono odpowiedzi, co się std::flush dzieje, oto kilka szczegółów na temat tego, co to jest. std::flushto manipulator , czyli funkcja z określoną sygnaturą. Na początek możesz pomyśleć std::flusho podpisie

std::ostream& std::flush(std::ostream&);

Rzeczywistość jest jednak nieco bardziej złożona (jeśli jesteś zainteresowany, wyjaśniono to również poniżej).

Klasa strumieniowa przeciąża operatory wyjściowe, przyjmując operatory tej postaci, tj. Istnieje funkcja składowa przyjmująca manipulator jako argument. Operator wyjściowy wywołuje manipulator z samym obiektem:

std::ostream& std::ostream::operator<< (std::ostream& (*manip)(std::ostream&)) {
    (*manip)(*this);
    return *this;
}

Oznacza to, że kiedy "wyprowadzasz" std::flushz do an std::ostream, wywołuje ona po prostu odpowiednią funkcję, tj. Poniższe dwie instrukcje są równoważne:

std::cout << std::flush;
std::flush(std::cout);

Teraz std::flush()samo jest dość proste: wszystko, co robi, to wywołanie std::ostream::flush(), tj. Możesz wyobrazić sobie jego implementację, aby wyglądała mniej więcej tak:

std::ostream& std::flush(std::ostream& out) {
    out.flush();
    return out;
}

std::ostream::flush()Funkcja technicznie wywołuje std::streambuf::pubsync()w buforze strumienia (jeśli w ogóle), co związane jest ze strumieniem: Bufor strumień jest odpowiedzialny za buforujące znaków i przesyłania znaków przeznaczenia zewnętrznej, kiedy bufor używane będzie przelewać lub gdy reprezentacją wewnętrzne powinny być synchronizowane z miejsce docelowe zewnętrzne, tj. kiedy dane mają zostać opróżnione. W sekwencyjnym strumieniu synchronizacja z zewnętrznym miejscem docelowym oznacza po prostu natychmiastowe wysyłanie wszystkich zbuforowanych znaków. Oznacza to, std::flushże użycie powoduje, że bufor strumienia opróżnia swój bufor wyjściowy. Na przykład, gdy dane są zapisywane w konsoli, opróżnianie powoduje, że znaki pojawiają się w tym miejscu konsoli.

Może to rodzić pytanie: dlaczego znaki nie są zapisywane od razu? Prosta odpowiedź jest taka, że ​​pisanie znaków jest na ogół dość powolne. Jednak czas potrzebny na napisanie rozsądnej liczby znaków jest zasadniczo identyczny z napisaniem tylko jednego gdzie. Liczba znaków zależy od wielu cech systemu operacyjnego, systemów plików itp., Ale często do około 4 tys. Znaków jest zapisywanych mniej więcej w tym samym czasie, co jeden znak. Zatem buforowanie znaków przed wysłaniem ich przy użyciu bufora w zależności od szczegółów zewnętrznego miejsca docelowego może być ogromnym wzrostem wydajności.

Powyższe odpowiedzi powinny odpowiedzieć na dwa z Twoich trzech pytań. Pozostaje pytanie: kiedy spłukiwałbyś strumień? Odpowiedź brzmi: kiedy znaki powinny być zapisywane w zewnętrznym miejscu docelowym! Może to być na końcu zapisanie pliku (zamykanie pliku niejawnie opróżnia bufor, choć) lub bezpośrednio przed prośbą o dane wprowadzone przez użytkownika (zauważ, że std::coutjest automatycznie spłukiwana podczas czytania od std::cinjak std::coutjest std::istream::tie()„d std::cin). Chociaż może być kilka sytuacji, w których wyraźnie chcesz przepłukać strumień, wydaje mi się, że są one dość rzadkie.

Na koniec obiecałem dać pełny obraz tego, co std::flushtak naprawdę jest: Strumienie są szablonami klas zdolnymi do radzenia sobie z różnymi typami postaci (w praktyce działają z chari wchar_t; zmuszanie ich do pracy z innymi postaciami jest dość skomplikowane, chociaż wykonalne, jeśli jesteś naprawdę zdeterminowany ). Aby móc używać go std::flushze wszystkimi wystąpieniami strumieni, jest to szablon funkcji z podpisem takim jak ten:

template <typename cT, typename Traits>
std::basic_ostream<cT, Traits>& std::flush(std::basic_ostream<cT, Traits>&);

W przypadku std::flushnatychmiastowego użycia z jego instancją std::basic_ostreamnie ma to większego znaczenia: kompilator automatycznie pobiera argumenty szablonu. Jednak w przypadkach, gdy ta funkcja nie jest wymieniona razem z czymś ułatwiającym dedukcję argumentów szablonu, kompilator nie będzie mógł wydedukować argumentów szablonu.

Dietmar Kühl
źródło
3
Fantastyczna odpowiedź. Nie udało się znaleźć informacji dotyczących jakości działania koloru nigdzie indziej.
Michael
32

Domyślnie std::coutjest buforowany, a rzeczywiste dane wyjściowe są drukowane tylko po zapełnieniu bufora lub wystąpieniu innej sytuacji opróżniania (np. Nowej linii w strumieniu). Czasami chcesz mieć pewność, że drukowanie nastąpi natychmiast i musisz przepłukać ręcznie.

Na przykład załóżmy, że chcesz zgłosić raport postępu, drukując pojedynczą kropkę:

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

Bez spłukiwania nie widziałbyś wyjścia przez bardzo długi czas.

Zauważ, że std::endlwstawia nowy wiersz do strumienia, a także powoduje jego wyrównanie. Ponieważ spłukiwanie jest umiarkowanie drogie, std::endlnie powinno być stosowane nadmiernie, jeśli nie jest to wyraźnie pożądane.

Kerrek SB
źródło
7
Tylko dodatkowa uwaga dla czytelników: coutto nie jedyna rzecz, która jest buforowana w C ++. ostreams na ogół są domyślnie buforowane, co obejmuje również fstreams i tym podobne.
Cornstalks
Również użycie cinwykonuje dane wyjściowe przed opróżnieniem, nie?
Zaid Khan
21

Oto krótki program, który możesz napisać, aby obserwować, co robi flush

#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;
}

Uruchom ten program: zauważysz, że wypisuje linię 1, zatrzymuje się, a następnie wypisuje linie 2 i 3. Teraz usuń wywołanie flush i uruchom program ponownie - zauważysz, że program zatrzymuje się, a następnie wypisuje wszystkie 3 linie w w tym samym czasie. Pierwsza linia jest buforowana przed zatrzymaniem programu, ale ponieważ bufor nigdy nie jest opróżniany, linia 1 jest wyprowadzana dopiero po wywołaniu endl z linii 2.

Neil Anderson
źródło
Innym sposobem zilustrowania tego jest cout << "foo" << flush; std::abort();. Jeśli skomentujesz / usuniesz << flush, NIE MA WYJŚCIA! PS: wywoływane biblioteki DLL ze standardowym wyjściem do debugowania abortto koszmar. Biblioteki DLL nigdy nie powinny wywoływać abort.
Mark Storer
5

Strumień jest z czymś połączony. W przypadku standardowego wyjścia może to być konsola / screen lub może zostać przekierowany do potoku lub pliku. Między programem a, na przykład, dyskiem twardym, na którym jest przechowywany plik, znajduje się dużo kodu. Na przykład system operacyjny obsługuje dowolny plik lub sam dysk twardy może buforować dane, aby móc zapisać je w blokach o stałym rozmiarze lub po prostu być bardziej wydajnym.

Kiedy opróżnisz strumień, informuje biblioteki językowe, system operacyjny i sprzęt, że chcesz, aby wszystkie znaki, które do tej pory wypisałeś, zostały wymuszone na całej drodze do pamięci. Teoretycznie po „spłukaniu” można by wyrzucić przewód ze ściany, a te postacie nadal byłyby bezpiecznie przechowywane.

Powinienem wspomnieć, że ludzie piszący sterowniki systemu operacyjnego lub ludzie projektujący napęd dyskowy mogą swobodnie użyć słowa „flush” jako sugestii i mogą nie wypisywać znaków. Nawet gdy wyjście jest zamknięte, mogą poczekać chwilę, aby je zapisać. (Pamiętaj, że system operacyjny wykonuje różne rzeczy na raz i może być bardziej efektywne odczekanie sekundy lub dwóch, aby obsłużyć twoje bajty).

Więc kolor jest rodzajem punktu kontrolnego.

Jeszcze jeden przykład: jeśli wyjście trafia na wyświetlacz konsoli, spłukiwanie sprawi, że znaki dotrą do miejsca, w którym użytkownik będzie mógł je zobaczyć. Jest to ważna rzecz, jeśli spodziewasz się wprowadzania danych z klawiatury. Jeśli myślisz, że napisałeś pytanie do konsoli i nadal utknęło gdzieś w jakimś wewnętrznym buforze, użytkownik nie wie, co wpisać w odpowiedzi. Tak więc jest to przypadek, w którym kolor jest ważny.

Lee Meador
źródło