Przywróć stan std :: cout po manipulowaniu nim

105

Załóżmy, że mam taki kod:

void printHex(std::ostream& x){
    x<<std::hex<<123;
}
..
int main(){
    std::cout<<100; // prints 100 base 10
    printHex(std::cout); //prints 123 in hex
    std::cout<<73; //problem! prints 73 in hex..
}

Moje pytanie brzmi, czy istnieje sposób „przywrócenia” stanu coutpierwotnego po powrocie z funkcji? (Trochę jak std::boolalphai std::noboolalpha...)?

Dzięki.

UltraInstinct
źródło
Uważam, że heks trwa tylko do następnej zmiany. Zmiana jest trwała tylko wtedy, gdy zmienisz flagi formatu ręcznie, zamiast używać manipulatorów.
Billy ONeal
4
@BillyONeal: Nie, użycie manipulatorów ma taki sam efekt, jak ręczna zmiana flag formatu. :-P
Chris Jester-Young
3
Jeśli jesteś tutaj z powodu ukrycia nie przywracania formatu ostream (STREAM_FORMAT_STATE) , zobacz Ustalanie pokrycia : Nie przywracanie formatu ostream (STREAM_FORMAT_STATE) .
jww
Zrobiłem coś podobnego - zobacz moje pytanie w Code Review: Użyj standardowego strumienia, a potem przywróć jego ustawienia .
Toby Speight
1
To pytanie jest doskonałym przykładem, dlaczego iostream nie jest lepszy niż stdio. Właśnie znalazłem dwa paskudne błędy z powodu nie- / częściowo / w pełni / nietrwałego iomanipa.
fuujuhi

Odpowiedzi:

97

musisz #include <iostream>lub #include <ios>wtedy w razie potrzeby:

std::ios_base::fmtflags f( cout.flags() );

//Your code here...

cout.flags( f );

Możesz umieścić je na początku i na końcu swojej funkcji lub sprawdzić tę odpowiedź, jak używać tego z RAII .

Stefan Kendall
źródło
5
@ ChrisJester-Young, właściwie dobry C ++ to RAII, szczególnie w takim przypadku jak ten!
Alexis Wilke
4
@Alexis W 100% się zgadzam. Zobacz moją odpowiedź (Zwiększ oszczędzanie stanu strumienia IO). :-)
Chris Jester-Young
3
To nie jest bezpieczne dla wyjątków.
einpoklum
2
Oprócz flag, stan strumienia to coś więcej.
jww
3
Możesz uniknąć tego problemu, nie wypychając formatów do strumieni. Wciśnij format i dane do tymczasowej zmiennej typu stringstream, a następnie wydrukuj
Mark Sherred
63

Doładowania IO Stream państwo oszczędzania wydaje się dokładnie to, czego potrzebują. :-)

Przykład na podstawie fragmentu kodu:

void printHex(std::ostream& x) {
    boost::io::ios_flags_saver ifs(x);
    x << std::hex << 123;
}
Chris Jester-Young
źródło
1
Zauważ, że nie ma tu żadnej magii, która ios_flags_saverpo prostu zapisuje i ustawia flagi, jak w odpowiedzi @ StefanKendall.
einpoklum
15
@einpoklum Ale w przeciwieństwie do innej odpowiedzi jest bezpieczny dla wyjątków. ;-)
Chris Jester-Young
2
Oprócz flag, stan strumienia to coś więcej.
jww
4
@jww Biblioteka IO Stream State Saver ma wiele klas do zapisywania różnych części stanu strumienia, z których ios_flags_saverjest tylko jedna.
Chris Jester-Young
3
Jeśli uważasz, że warto samodzielnie wdrażać i utrzymywać każdy drobiazg, zamiast korzystać z sprawdzonej, dobrze przetestowanej biblioteki ...
jupp0r,
45

Zwróć uwagę, że przedstawione tutaj odpowiedzi nie przywrócą pełnego stanu std::cout. Na przykład std::setfillbędzie się „trzymać” nawet po sprawdzeniu .flags(). Lepszym rozwiązaniem jest użycie .copyfmt:

std::ios oldState(nullptr);
oldState.copyfmt(std::cout);

std::cout
    << std::hex
    << std::setw(8)
    << std::setfill('0')
    << 0xDECEA5ED
    << std::endl;

std::cout.copyfmt(oldState);

std::cout
    << std::setw(15)
    << std::left
    << "case closed"
    << std::endl;

Wydrukuje:

case closed

zamiast:

case closed0000
rr-
źródło
Chociaż odpowiedź na moje oryginalne pytanie została udzielona kilka lat temu, ta odpowiedź jest świetnym dodatkiem. :-)
UltraInstinct
2
@UltraInstinct Wydaje się, że jest to lepsze rozwiązanie, w takim przypadku możesz i prawdopodobnie powinieneś uczynić to akceptowaną odpowiedzią.
underscore_d
To z pewnych powodów zgłasza wyjątek, jeśli wyjątki są włączone dla strumienia. coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
anton_rh
1
Wygląda na std::iosto, że jest zawsze w złym stanie, ponieważ ma NULLrdbuf. Zatem ustawienie stanu z włączonymi wyjątkami powoduje zgłaszanie wyjątków z powodu złego stanu. Rozwiązania: 1) Użyj jakiejś klasy (na przykład std::stringstream) z rdbufzestawem zamiast std::ios. 2) Zapisz stan wyjątków osobno do zmiennej lokalnej i wyłącz je wcześniej state.copyfmt, a następnie przywróć wyjątek ze zmiennej (i zrób to ponownie po przywróceniu stanu, z oldStatektórego wyjątki są wyłączone). 3) Zestaw rdbufdo std::iossłuszne:struct : std::streambuf {} sbuf; std::ios oldState(&sbuf);
anton_rh
22

Utworzyłem klasę RAII, używając przykładowego kodu z tej odpowiedzi. Dużą zaletą tej techniki jest sytuacja, gdy masz wiele ścieżek powrotu z funkcji, która ustawia flagi w iostream. Niezależnie od używanej ścieżki powrotnej, destruktor będzie zawsze wywoływany, a flagi zawsze będą resetowane. Nie można zapomnieć o przywróceniu flag, gdy funkcja powróci.

class IosFlagSaver {
public:
    explicit IosFlagSaver(std::ostream& _ios):
        ios(_ios),
        f(_ios.flags()) {
    }
    ~IosFlagSaver() {
        ios.flags(f);
    }

    IosFlagSaver(const IosFlagSaver &rhs) = delete;
    IosFlagSaver& operator= (const IosFlagSaver& rhs) = delete;

private:
    std::ostream& ios;
    std::ios::fmtflags f;
};

Można go następnie użyć, tworząc lokalną instancję IosFlagSaver, ilekroć chcesz zapisać bieżący stan flagi. Gdy to wystąpienie znajdzie się poza zakresem, stan bandery zostanie przywrócony.

void f(int i) {
    IosFlagSaver iosfs(std::cout);

    std::cout << i << " " << std::hex << i << " ";
    if (i < 100) {
        std::cout << std::endl;
        return;
    }
    std::cout << std::oct << i << std::endl;
}
qbert220
źródło
2
Świetnie, jeśli ktoś rzuca, nadal masz prawidłowe flagi w swoim strumieniu.
Alexis Wilke
4
Oprócz flag, stan strumienia to coś więcej.
jww
1
Naprawdę chciałbym, żeby C ++ mógł spróbować / wreszcie. To doskonały przykład, w którym RAII działa, ale ostatecznie byłby prostszy.
Trade-Ideas Philip
2
Jeśli twój projekt jest przynajmniej trochę rozsądny, masz Boost i do tego celu dołączone są oszczędzacze stanu .
Jan Hudec
9

Z niewielkimi modyfikacjami, aby wynik był bardziej czytelny:

void printHex(std::ostream& x) {
   ios::fmtflags f(x.flags());
   x << std::hex << 123 << "\n";
   x.flags(f);
}

int main() {
    std::cout << 100 << "\n"; // prints 100 base 10
    printHex(std::cout);      // prints 123 in hex
    std::cout << 73 << "\n";  // problem! prints 73 in hex..
}
whacko__Cracko
źródło
9

Możesz utworzyć inną otokę wokół bufora stdout:

#include <iostream>
#include <iomanip>
int main() {
    int x = 76;
    std::ostream hexcout (std::cout.rdbuf());
    hexcout << std::hex;
    std::cout << x << "\n"; // still "76"
    hexcout << x << "\n";   // "4c"
}

W funkcji:

void print(std::ostream& os) {
    std::ostream copy (os.rdbuf());
    copy << std::hex;
    copy << 123;
}

Oczywiście, jeśli problemem jest wydajność, jest to trochę droższe, ponieważ polega na kopiowaniu całego iosobiektu (ale nie bufora), w tym niektórych rzeczy, za które płacisz, ale prawdopodobnie nie używasz, takich jak ustawienia regionalne.

W przeciwnym razie czuję, że jeśli zamierzasz używać .flags(), lepiej być konsekwentnym i używać .setf()również zamiast <<składni (czysta kwestia stylu).

void print(std::ostream& os) {
    std::ios::fmtflags os_flags (os.flags());
    os.setf(std::ios::hex);
    os << 123;
    os.flags(os_flags);
}

Jak powiedzieli inni, możesz umieścić powyższe (i .precision()i .fill(), ale zwykle nie dotyczy to ustawień regionalnych i słów, które zwykle nie będą modyfikowane i są cięższe) w klasie dla wygody i aby były bezpieczne; konstruktor powinien zaakceptować std::ios&.

n.caillou
źródło
Słuszna uwaga [+], ale oczywiście pamięta o używaniu std::stringstreamdo formatowania, jak wskazał Mark Sherred .
Wolf
@Wolf Nie jestem pewien, czy rozumiem. An std::stringstream jest an std:ostream, z wyjątkiem tego, że użycie one wprowadza dodatkowy bufor pośredni.
n.caillou
Oczywiście oba są poprawnymi podejściami do formatowania danych wyjściowych, oba przedstawiają obiekt stream, ten, który opisujesz, jest dla mnie nowy. Muszę teraz pomyśleć o zaletach i wadach. Jednak inspirujące pytanie z pouczającymi odpowiedziami ... (mam na myśli wariant kopiowania strumieniowego)
Wolf
1
Nie możesz skopiować strumienia, ponieważ kopiowanie buforów często nie ma sensu (np. Stdout). Możesz jednak mieć kilka obiektów strumieniowych dla tego samego bufora, co proponuje ta odpowiedź. Natomiast std:stringstreamwola stworzy własną niezależną std:stringbuf( std::streambufpochodną), którą następnie trzeba wlaćstd::cout.rdbuf()
n.caillou
Dziękuję za wyjaśnienie.
Wolf
0

Chciałbym nieco uogólnić odpowiedź z qbert220:

#include <ios>

class IoStreamFlagsRestorer
{
public:
    IoStreamFlagsRestorer(std::ios_base & ioStream)
        : ioStream_(ioStream)
        , flags_(ioStream_.flags())
    {
    }

    ~IoStreamFlagsRestorer()
    {
        ioStream_.flags(flags_);
    }

private:
    std::ios_base & ioStream_;
    std::ios_base::fmtflags const flags_;
};

Powinno to działać również w przypadku strumieni wejściowych i innych.

PS: Chciałbym to zrobić po prostu jako komentarz do powyższej odpowiedzi, jednak stackoverflow nie pozwala mi na to z powodu braku reputacji. W ten sposób zaśmiecę tutaj odpowiedzi zamiast prostego komentarza ...

J. Wilde
źródło