Ripristina lo stato di std :: cout dopo averlo manipolato


105

Supponiamo che io abbia un codice come questo:

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

La mia domanda è se esiste un modo per "ripristinare" lo stato di coutquello originale dopo essere tornati dalla funzione? (Un po 'come std::boolalphaestd::noboolalpha ..)?

Grazie.


Credo che la maledizione duri solo per la prossima operazione di trasferimento. La modifica è persistente solo se si modificano manualmente i flag di formato invece di utilizzare i manipolatori.
Billy ONeal

4
@BillyONeal: No, l'uso dei manipolatori ha lo stesso effetto della modifica manuale dei flag di formato. :-P
Chris Jester-Young,

3
Se sei qui a causa di una scoperta di Covertiy che non ripristina il formato ostream (STREAM_FORMAT_STATE) , vedi Ricerca di coverity: non ripristina il formato ostream (STREAM_FORMAT_STATE) .
jww

Ho fatto qualcosa di simile: vedi la mia domanda su Code Review: usa uno stream standard e ripristina le sue impostazioni in seguito .
Toby Speight

1
Questa domanda è un perfetto esempio del perché iostream non è migliore di stdio. Ho appena trovato due brutti bug a causa di iomanip persistente non / semi- / completamente / cosa-non.
Fuujuhi

Risposte:


97

è necessario #include <iostream>o #include <ios>poi quando richiesto:

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

//Your code here...

cout.flags( f );

Puoi metterli all'inizio e alla fine della tua funzione, o dai un'occhiata a questa risposta su come usarlo con RAII .


5
@ ChrisJester-Young, in realtà un buon C ++ è RAII, specialmente in un caso come questo!
Alexis Wilke,

4
@Alexis Sono d'accordo al 100%. Vedi la mia risposta (Boost IO Stream State Saver). :-)
Chris Jester-Young

3
Questo non è sicuro rispetto alle eccezioni.
einpoklum

2
C'è di più nello stato del flusso oltre alle bandiere.
jww

3
È possibile evitare il problema non trasferendo i formati negli stream. Inserisci il formato ei dati in una variabile di flusso di stringhe temporanea, quindi stampa
Mark Sherred

63

Il Boost IO Stream State Saver sembra esattamente ciò di cui hai bisogno. :-)

Esempio basato sullo snippet di codice:

void printHex(std::ostream& x) {
    boost::io::ios_flags_saver ifs(x);
    x << std::hex << 123;
}

1
Nota che non c'è magia qui, che ios_flags_saverfondamentalmente salva e imposta i flag come nella risposta di @ StefanKendall.
einpoklum

15
@einpoklum Ma è sicuro rispetto alle eccezioni, a differenza dell'altra risposta. ;-)
Chris Jester-Young

2
C'è di più nello stato del flusso oltre alle bandiere.
jww

4
@jww La libreria IO Stream State Saver ha più classi, per salvare diverse parti dello stato del flusso, di cui ios_flags_saversolo una.
Chris Jester-Young

3
Se pensi che valga la pena reimplementare e mantenere ogni piccola cosa da solo, invece di utilizzare una libreria revisionata e ben collaudata ...
jupp0r

45

Nota che le risposte presentate qui non ripristineranno lo stato completo di std::cout. Ad esempio, std::setfill"rimarrà" anche dopo aver chiamato .flags(). Una soluzione migliore è usare .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;

Stamperà:

case closed

piuttosto che:

case closed0000

Sebbene la mia domanda originale abbia avuto risposta alcuni anni fa, questa risposta è un'ottima aggiunta. :-)
UltraInstinct

2
@UltraInstinct Sembra essere una soluzione migliore , nel qual caso puoi e probabilmente dovresti renderla invece la risposta accettata.
underscore_d

Ciò per alcuni motivi genera un'eccezione se le eccezioni sono abilitate per il flusso. coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
anton_rh

1
Sembra che std::iossia sempre in cattivo stato perché ha NULLrdbuf. Pertanto, l'impostazione di uno stato con le eccezioni abilitate causa il lancio di eccezioni a causa di uno stato errato. Soluzioni: 1) Usa una classe (ad esempio std::stringstream) con rdbufset invece di std::ios. 2) Salva lo stato delle eccezioni separatamente nella variabile locale e disabilitalo prima state.copyfmt, quindi ripristina l'eccezione dalla variabile (e fallo di nuovo dopo aver ripristinato lo stato da oldStatecui le eccezioni sono disabilitate). 3) Insieme rdbufa std::iosquesto modo:struct : std::streambuf {} sbuf; std::ios oldState(&sbuf);
anton_rh

22

Ho creato una classe RAII usando il codice di esempio da questa risposta. Il grande vantaggio di questa tecnica si ha se si hanno più percorsi di ritorno da una funzione che imposta i flag su un iostream. Qualunque sia il percorso di ritorno utilizzato, il distruttore verrà sempre chiamato e i flag verranno sempre ripristinati. Non c'è possibilità di dimenticare di ripristinare i flag quando la funzione ritorna.

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

Dovresti quindi usarlo creando un'istanza locale di IosFlagSaver ogni volta che desideri salvare lo stato di flag corrente. Quando questa istanza esce dall'ambito, lo stato flag verrà ripristinato.

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
Eccellente, se qualcuno lancia, hai ancora i flag corretti nel tuo stream.
Alexis Wilke,

4
C'è di più nello stato del flusso oltre alle bandiere.
jww

1
Vorrei davvero che C ++ consentisse di provare / finalmente. Questo è un ottimo esempio in cui RAII funziona, ma alla fine sarebbe stato più semplice.
Trade-Ideas Philip

2
Se il tuo progetto è almeno un po 'sano, hai Boost e questo viene fornito con risparmiatori di stato per questo scopo.
Jan Hudec

9

Con qualche modifica per rendere l'output più leggibile:

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

Puoi creare un altro wrapper attorno al buffer 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"
}

In una funzione:

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

Ovviamente se le prestazioni sono un problema, questo è un po 'più costoso perché sta copiando l'intero ios oggetto (ma non il buffer) includendo alcune cose che stai pagando ma che è improbabile che tu usi come la locale.

Altrimenti penso che se hai intenzione di usarlo .flags()è meglio essere coerenti e usare .setf()anche piuttosto che la <<sintassi (pura questione di stile).

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

Come altri hanno già detto, puoi mettere quanto sopra (e .precision()e .fill(), ma in genere non il locale e le cose relative alle parole che di solito non verranno modificate ed è più pesante) in una classe per comodità e per renderla sicura rispetto alle eccezioni; il costruttore dovrebbe accettare std::ios&.


Buon punto [+], ma ovviamente si ricorda di usare std::stringstreamper la parte di formattazione come ha sottolineato Mark Sherred .
Wolf

@ Wolf, non sono sicuro di aver capito il tuo punto. An std::stringstream è an std:ostream, tranne che l'uso di uno introduce un buffer intermedio aggiuntivo.
n.caillou

Ovviamente entrambi sono approcci validi alla formattazione dell'output, entrambi introducono un oggetto stream, quello che descrivi è nuovo per me. Adesso devo pensare a pro e contro. Tuttavia, una domanda stimolante con risposte illuminanti ... (intendo la variante della copia in streaming)
Wolf

1
Non è possibile copiare uno stream, perché la copia dei buffer spesso non ha senso (es. Stdout). Tuttavia, puoi avere diversi oggetti flusso per lo stesso buffer, che è ciò che questa risposta propone di fare. Mentre un std:stringstreamcreerà il proprio indipendente std:stringbuf(un std::streambufderivato), che poi deve essere versato instd::cout.rdbuf()
n.caillou

Grazie per il chiarimento.
Wolf,

0

Vorrei generalizzare in qualche modo la risposta di 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_;
};

Questo dovrebbe funzionare anche per i flussi di input e altri.

PS: mi sarebbe piaciuto fare questo semplicemente un commento alla risposta sopra, stackoverflow tuttavia non mi consente di farlo a causa della mancanza di reputazione. Quindi fammi confondere le risposte qui invece di un semplice commento ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.