Restaurar el estado de std :: cout después de manipularlo

105

Supongamos que tengo un código como este:

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

Mi pregunta es si hay alguna forma de 'restaurar' el estado couta su estado original después de regresar de la función. (Algo así como std::boolalphay std::noboolalpha..)?

Gracias.

UltraInstinto
fuente
Creo que el maleficio solo dura para la siguiente operación de turno. El cambio solo es persistente si cambia los indicadores de formato manualmente en lugar de utilizar manipuladores.
Billy ONeal
4
@BillyONeal: No, usar manipuladores tiene el mismo efecto que cambiar las banderas de formato manualmente. :-P
Chris Jester-Young
3
Si estás aquí debido a un hallazgo encubierto que no restaura el formato ostream (STREAM_FORMAT_STATE) , consulta el hallazgo de Coverity: no restaura el formato ostream (STREAM_FORMAT_STATE) .
jww
Hice algo similar: vea mi pregunta en Revisión de código: use una transmisión estándar y restaure su configuración después .
Toby Speight
1
Esta pregunta es un ejemplo perfecto de por qué iostream no es mejor que stdio. Acabo de encontrar dos errores desagradables debido a iomanip no persistente / semi / completamente / qué no persistente.
fuujuhi

Respuestas:

97

necesita #include <iostream>o #include <ios>luego cuando sea necesario:

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

//Your code here...

cout.flags( f );

Puede colocarlos al principio y al final de su función, o consulte esta respuesta sobre cómo usar esto con RAII .

Stefan Kendall
fuente
5
@ ChrisJester-Young, realmente un buen C ++ es RAII, ¡especialmente en un caso como este!
Alexis Wilke
4
@Alexis Estoy 100% de acuerdo. Vea mi respuesta (Boost IO Stream State Saver). :-)
Chris Jester-Young
3
Esto no es seguro para excepciones.
einpoklum
2
Hay más en el estado de la transmisión además de las banderas.
jww
3
Puede evitar el problema si no inserta formatos en las transmisiones. Inserte el formato y los datos en una variable de secuencia temporal, luego imprima
Mark Sherred
63

El Boost IO estado de la secuencia de ahorro parece exactamente lo que necesita. :-)

Ejemplo basado en su fragmento de código:

void printHex(std::ostream& x) {
    boost::io::ios_flags_saver ifs(x);
    x << std::hex << 123;
}
Chris Jester-Young
fuente
1
Tenga en cuenta que aquí no hay magia, que ios_flags_saverbásicamente solo guarda y establece las banderas como en la respuesta de @ StefanKendall.
einpoklum
15
@einpoklum Pero es seguro para excepciones, a diferencia de la otra respuesta. ;-)
Chris Jester-Young
2
Hay más en el estado de la transmisión además de las banderas.
jww
4
@jww La biblioteca IO Stream State Saver tiene varias clases, para guardar diferentes partes del estado de la secuencia, de las cuales ios_flags_saveres solo una.
Chris Jester-Young
3
Si cree que vale la pena
volver a implementar
45

Tenga en cuenta que las respuestas presentadas aquí no restaurarán el estado completo de std::cout. Por ejemplo, std::setfillse "pegará" incluso después de llamar .flags(). Una mejor solución es usar .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;

Imprimirá:

case closed

más bien que:

case closed0000
rr-
fuente
Aunque mi pregunta original ha sido respondida hace unos años, esta respuesta es una gran adición. :-)
UltraInstinct
2
@UltraInstinct Parece ser una mejor solución, en cuyo caso, puede y probablemente debería convertirla en la respuesta aceptada.
underscore_d
Esto, por algunas razones, genera una excepción si las excepciones están habilitadas para la transmisión. coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
anton_rh
1
Parece que std::iossiempre está en mal estado porque tiene NULLrdbuf. Por lo tanto, establecer un estado con excepciones habilitadas provoca el lanzamiento de excepciones debido a un mal estado. Soluciones: 1) Use alguna clase (por ejemplo std::stringstream) con rdbufset en lugar de std::ios. 2) Guarde el estado de las excepciones por separado en la variable local y deshabilítelas antes state.copyfmt, luego restaure la excepción de la variable (y haga esto nuevamente después de restaurar el estado desde el oldStatecual tiene las excepciones deshabilitadas). 3) Conjunto rdbufpara std::iosde esta manera:struct : std::streambuf {} sbuf; std::ios oldState(&sbuf);
anton_rh
22

Creé una clase RAII usando el código de ejemplo de esta respuesta. La gran ventaja de esta técnica viene si tiene múltiples rutas de retorno de una función que establece banderas en un iostream. Cualquiera que sea la ruta de retorno que se use, siempre se llamará al destructor y las banderas siempre se restablecerán. No hay posibilidad de olvidar restaurar las banderas cuando la función regrese.

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

Luego lo usaría creando una instancia local de IosFlagSaver siempre que quisiera guardar el estado actual de la bandera. Cuando esta instancia salga del alcance, se restaurará el estado de la bandera.

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
fuente
2
Excelente, si alguien lanza, todavía tienes las banderas correctas en tu transmisión.
Alexis Wilke
4
Hay más en el estado de la transmisión además de las banderas.
jww
1
Realmente desearía que C ++ permitiera probar / finalmente. Este es un excelente ejemplo donde funciona RAII, pero finalmente hubiera sido más simple.
Trade-Ideas Philip
2
Si su proyecto es al menos un poco cuerdo, tiene Boost y eso viene con ahorradores estatales para este propósito.
Jan Hudec
9

Con un poco de modificación para que la salida sea más legible:

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
fuente
9

Puede crear otra envoltura alrededor del búfer 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"
}

En una función:

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

Por supuesto, si el rendimiento es un problema, esto es un poco más caro porque copia todo el iosobjeto (pero no el búfer), incluidas algunas cosas por las que está pagando pero que es poco probable que use, como la configuración regional.

De lo contrario, siento que si vas a usar .flags()es mejor ser coherente y usar .setf()también en lugar de la <<sintaxis (pura cuestión de estilo).

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

Como han dicho otros, puede poner lo anterior (y .precision()y .fill(), pero normalmente no las cosas relacionadas con la configuración regional y las palabras que generalmente no se van a modificar y son más pesadas) en una clase por conveniencia y para que sea seguro para excepciones; el constructor debería aceptar std::ios&.

n.caillou
fuente
Buen punto [+], pero por supuesto recuerda usarlo std::stringstreampara la parte de formato, como señaló Mark Sherred .
Wolf
@Wolf No estoy seguro de entender tu punto. An std::stringstream es un std:ostream, excepto que el uso de uno introduce un búfer intermedio adicional.
n.caillou
Por supuesto, ambos son enfoques válidos para formatear la salida, ambos introducen un objeto de flujo, el que usted describe es nuevo para mí. Ahora tengo que pensar en pros y contras. Sin embargo, una pregunta inspiradora con respuestas esclarecedoras ... (me refiero a la variante de copia de flujo)
Wolf
1
No puede copiar una secuencia, porque copiar búferes a menudo no tiene sentido (por ejemplo, stdout). Sin embargo, puede tener varios objetos de flujo para el mismo búfer, que es lo que propone esta respuesta. Mientras que un std:stringstreamcreará su propio independiente std:stringbuf(un std::streambufderivado), que luego debe verterse enstd::cout.rdbuf()
n.caillou
Gracias por la aclaración.
Wolf
0

Me gustaría generalizar un poco la respuesta de 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_;
};

Esto debería funcionar para flujos de entrada y otros también.

PD: Me hubiera gustado hacer esto simplemente un comentario a la respuesta anterior, sin embargo, stackoverflow no me permite hacerlo debido a la falta de reputación. Por lo tanto, hazme desordenar las respuestas aquí en lugar de un simple comentario ...

J. Wilde
fuente