Czy ktoś może wyjaśnić (najlepiej używając zwykłego angielskiego), jak std::flush
działa?
- Co to jest?
- Kiedy spłukiwałbyś strumień?
- Dlaczego to jest ważne?
Dziękuję Ci.
Odpowiedzi:
Ponieważ nie udzielono odpowiedzi, co się std::flush
dzieje, oto kilka szczegółów na temat tego, co to jest. std::flush
to manipulator , czyli funkcja z określoną sygnaturą. Na początek możesz pomyśleć std::flush
o 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::flush
z 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::cout
jest automatycznie spłukiwana podczas czytania od std::cin
jak std::cout
jest 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::flush
tak naprawdę jest: Strumienie są szablonami klas zdolnymi do radzenia sobie z różnymi typami postaci (w praktyce działają z char
i 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::flush
ze 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::flush
natychmiastowego użycia z jego instancją std::basic_ostream
nie 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.
Domyślnie std::cout
jest 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::endl
wstawia nowy wiersz do strumienia, a także powoduje jego wyrównanie. Ponieważ spłukiwanie jest umiarkowanie drogie, std::endl
nie powinno być stosowane nadmiernie, jeśli nie jest to wyraźnie pożądane.
cout
to nie jedyna rzecz, która jest buforowana w C ++. ostream
s na ogół są domyślnie buforowane, co obejmuje również fstream
s i tym podobne.
cin
wykonuje dane wyjściowe przed opróżnieniem, nie?
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.
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 abort
to koszmar. Biblioteki DLL nigdy nie powinny wywoływać abort
.
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.