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.


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 .


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

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

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

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..
}

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&.


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 ...

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.