Restaurez l'état de std :: cout après l'avoir manipulé


105

Supposons que j'ai un code comme celui-ci:

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

Ma question est de savoir s'il existe un moyen de «restaurer» l'état de coutà son état d' origine après le retour de la fonction? (Un peu comme std::boolalphaet std::noboolalpha..)?

Merci.


Je crois que hexagone ne dure que pour la prochaine opération de départ. La modification n'est persistante que si vous modifiez les indicateurs de format manuellement au lieu d'utiliser des manipulateurs.
Billy ONeal

4
@BillyONeal: Non, l'utilisation de manipulateurs a le même effet que la modification manuelle des indicateurs de format. :-P
Chris Jester-Young

3
Si vous êtes ici en raison d'une découverte secrète Ne pas restaurer le format ostream (STREAM_FORMAT_STATE) , consultez Recherche de couverture: pas de restauration du format ostream (STREAM_FORMAT_STATE) .
jww

J'ai fait quelque chose de similaire - voir ma question sur la révision du code: utilisez un flux standard et restaurez ses paramètres par la suite .
Toby Speight

1
Cette question est un exemple parfait de la raison pour laquelle iostream n'est pas meilleur que stdio. Je viens de trouver deux bugs désagréables à cause de iomanip non / semi-/ entièrement / quoi-persistant.
fuujuhi

Réponses:


97

vous devez #include <iostream>ou #include <ios>alors si nécessaire:

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

//Your code here...

cout.flags( f );

Vous pouvez les mettre au début et à la fin de votre fonction, ou consultez cette réponse pour savoir comment l'utiliser avec RAII .


5
@ ChrisJester-Young, en fait un bon C ++ est RAII, surtout dans un cas comme celui-ci!
Alexis Wilke

4
@Alexis Je suis à 100% d'accord. Voir ma réponse (Boost IO Stream State Saver). :-)
Chris Jester-Young

3
Ce n'est pas sans danger pour les exceptions.
einpoklum

2
Il y a plus à l'état du flux en plus des drapeaux.
jww

3
Vous pouvez éviter le problème en ne poussant pas les formats sur les flux. Poussez le format et les données dans une variable de flux de chaînes temporaire, puis imprimez
Mark Sherred

63

L' économiseur d'état Boost IO Stream semble exactement ce dont vous avez besoin. :-)

Exemple basé sur votre extrait de code:

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

1
Notez qu'il n'y a pas de magie ici, qui ios_flags_saversauvegarde et définit simplement les indicateurs comme dans la réponse de @ StefanKendall.
einpoklum

15
@einpoklum Mais il est sans danger pour les exceptions, contrairement à l'autre réponse. ;-)
Chris Jester-Young

2
Il y a plus à l'état du flux en plus des drapeaux.
jww

4
@jww La bibliothèque IO Stream State Saver a plusieurs classes, pour enregistrer différentes parties de l'état du flux, dont ios_flags_saverune seule.
Chris Jester-Young

3
Si vous pensez qu'il vaut la peine de réimplémenter et de maintenir tout par vous-même, au lieu d'utiliser une bibliothèque revue et bien testée ...
jupp0r

45

Notez que les réponses présentées ici ne restaureront pas l'état complet de std::cout. Par exemple, std::setfill"collera" même après avoir appelé .flags(). Une meilleure solution consiste à utiliser .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;

Imprimera:

case closed

plutôt que:

case closed0000

Bien que ma question initiale ait reçu une réponse il y a quelques années, cette réponse est un excellent ajout. :-)
UltraInstinct

2
@UltraInstinct Cela semble être une meilleure solution, auquel cas vous pouvez et devriez probablement en faire la réponse acceptée à la place.
underscore_d

Cela pour certaines raisons lève une exception si des exceptions sont activées pour le flux. coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
anton_rh

1
Il semble que ce std::iossoit toujours en mauvais état car il a NULLrdbuf. Ainsi, la définition d'un état avec des exceptions activées entraîne la levée d'exceptions en raison d'un mauvais état. Solutions: 1) Utilisez une classe (par exemple std::stringstream) avec rdbufset au lieu de std::ios. 2) Enregistrez l'état des exceptions séparément dans la variable locale et désactivez-les avant state.copyfmt, puis restaurez l'exception à partir de la variable (et recommencez après la restauration de l'état à partir oldStateduquel les exceptions sont désactivées). 3) Situé rdbufà std::ioscomme ceci:struct : std::streambuf {} sbuf; std::ios oldState(&sbuf);
anton_rh

22

J'ai créé une classe RAII en utilisant l'exemple de code de cette réponse. Le gros avantage de cette technique vient si vous avez plusieurs chemins de retour à partir d'une fonction qui définit des indicateurs sur un iostream. Quel que soit le chemin de retour utilisé, le destructeur sera toujours appelé et les indicateurs seront toujours réinitialisés. Il n'y a aucune chance d'oublier de restaurer les drapeaux lorsque la fonction revient.

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

Vous l'utiliseriez ensuite en créant une instance locale de IosFlagSaver chaque fois que vous vouliez enregistrer l'état actuel du drapeau. Lorsque cette instance sort du champ d'application, l'état du drapeau est restauré.

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
Excellent, si quelqu'un lance, vous avez toujours les bons indicateurs dans votre flux.
Alexis Wilke

4
Il y a plus à l'état du flux en plus des drapeaux.
jww

1
Je souhaite vraiment que C ++ permette d'essayer / enfin. C'est un excellent exemple où RAII fonctionne, mais aurait finalement été plus simple.
Trade-Ideas Philip

2
Si votre projet est au moins un peu sain d'esprit, vous avez Boost et qui est livré avec des économiseurs d'état à cet effet.
Jan Hudec

9

Avec un peu de modification pour rendre la sortie plus lisible:

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

Vous pouvez créer un autre wrapper autour du tampon 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"
}

Dans une fonction:

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

Bien sûr, si les performances sont un problème, cela coûte un peu plus cher car il copie l' iosobjet entier (mais pas le tampon), y compris certains éléments pour lesquels vous payez mais qu'il est peu probable que vous utilisiez, comme les paramètres régionaux.

Sinon, j'ai l'impression que si vous allez l'utiliser, .flags()il vaut mieux être cohérent et utiliser .setf()aussi plutôt que la <<syntaxe (pure question de style).

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

Comme d'autres l'ont dit, vous pouvez mettre les éléments ci-dessus (et .precision()et .fill(), mais généralement pas les éléments relatifs aux paramètres régionaux et aux mots qui ne seront généralement pas modifiés et qui sont plus lourds) dans une classe pour plus de commodité et pour la rendre sûre pour les exceptions; le constructeur doit accepter std::ios&.


Bon point [+], mais il se souvient bien sûr de l'utilisation std::stringstreampour la partie mise en forme comme l'a souligné Mark Sherred .
Wolf

@Wolf Je ne suis pas sûr d'avoir compris votre point de vue. An std::stringstream est un std:ostream, sauf que l'utilisation de l'un introduit un tampon intermédiaire supplémentaire.
n.caillou

Bien sûr, les deux sont des approches valides pour formater la sortie, les deux introduisent un objet de flux, celui que vous décrivez est nouveau pour moi. Je dois penser aux avantages et aux inconvénients maintenant. Cependant, une question inspirante avec des réponses éclairantes ... (je veux dire la variante de copie de flux)
Wolf

1
Vous ne pouvez pas copier un flux, car la copie de tampons n'a souvent pas de sens (par exemple, stdout). Cependant, vous pouvez avoir plusieurs objets de flux pour le même tampon, ce que cette réponse propose de faire. Alors qu'un std:stringstreamva créer son propre indépendant std:stringbuf(un std::streambufdérivé), qui doit ensuite être versé dansstd::cout.rdbuf()
n.caillou

Merci pour la clarification.
Wolf

0

Je voudrais généraliser quelque peu la réponse 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_;
};

Cela devrait également fonctionner pour les flux d'entrée et autres.

PS: J'aurais aimé faire ceci simplement un commentaire à la réponse ci-dessus, stackoverflow ne me permet cependant pas de le faire à cause d'une réputation manquante. Alors faites-moi encombrer les réponses ici au lieu d'un simple commentaire ...

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.