Restaure o estado de std :: cout após manipulá-lo

105

Suponha que eu tenha um 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..
}

Minha pergunta é se há alguma maneira de 'restaurar' o estado coutoriginal após retornar da função? (Um pouco como std::boolalphae std::noboolalpha..)?

Obrigado.

UltraInstinct
fonte
Eu acredito que hex só dura para a próxima operação de mudança. A mudança só é persistente se você mudar os sinalizadores de formato manualmente em vez de usar manipuladores.
Billy ONeal de
4
@BillyONeal: Não, usar manipuladores tem o mesmo efeito que mudar os sinalizadores de formato manualmente. :-P
Chris Jester-Young
3
Se você está aqui devido a um achado secreto de Não restaurar o formato ostream (STREAM_FORMAT_STATE) , então veja a descoberta de Coverity: Não restaurar o formato ostream (STREAM_FORMAT_STATE) .
jww
Fiz algo semelhante - veja minha pergunta na revisão de código: use um fluxo padrão e restaure suas configurações depois .
Toby Speight
1
Esta pergunta é um exemplo perfeito de por que iostream não é melhor do que stdio. Acabei de encontrar dois bugs desagradáveis ​​por causa do iomanip não- / semi- / totalmente- / what-not persistente.
fuujuhi

Respostas:

97

você precisa #include <iostream>ou #include <ios>então quando necessário:

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

//Your code here...

cout.flags( f );

Você pode colocá-los no início e no final de sua função, ou verificar esta resposta sobre como usar isso com RAII .

Stefan Kendall
fonte
5
@ ChrisJester-Young, C ++ realmente bom é RAII, especialmente em um caso como este!
Alexis Wilke
4
@Alexis Eu concordo 100%. Veja minha resposta (Boost IO Stream State Saver). :-)
Chris Jester-Young
3
Isso não é seguro para exceções.
einpoklum
2
Há mais informações sobre o estado do stream além das bandeiras.
jww
3
Você pode evitar o problema não colocando formatos em streams. Envie o formato e os dados para uma variável stringstream temporária e imprima
Mark Sherred
63

O protetor de estado do fluxo de IO Boost parece exatamente o que você precisa. :-)

Exemplo com base em seu snippet de código:

void printHex(std::ostream& x) {
    boost::io::ios_flags_saver ifs(x);
    x << std::hex << 123;
}
Chris Jester-Young
fonte
1
Observe que não há mágica aqui, que ios_flags_saverbasicamente apenas salva e define os sinalizadores como na resposta de @ StefanKendall.
einpoklum
15
@einpoklum Mas é seguro com exceção, ao contrário da outra resposta. ;-)
Chris Jester-Young
2
Há mais informações sobre o estado do stream além das bandeiras.
jww
4
@jww A biblioteca IO Stream State Saver tem várias classes, para salvar diferentes partes do estado do stream, das quais ios_flags_saveré apenas uma.
Chris Jester-Young
3
Se você acha que vale a pena reimplementar e manter tudo sozinho, em vez de usar uma biblioteca revisada e bem testada ...
jupp0r
45

Observe que as respostas apresentadas aqui não restaurarão o estado completo de std::cout. Por exemplo, std::setfillvai "grudar" mesmo depois de chamar .flags(). A melhor solução é 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;

Irá imprimir:

case closed

ao invés de:

case closed0000
rr-
fonte
Embora minha pergunta original tenha sido respondida há alguns anos, essa resposta é uma grande adição. :-)
UltraInstinct de
2
@UltraInstinct Parece ser a melhor solução; nesse caso, você pode e provavelmente deve torná-la a resposta aceita.
sublinhado_d
Isso, por alguns motivos, lança exceção se as exceções estiverem habilitadas para o fluxo. coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
anton_rh
1
Parece que std::iosestá sempre em mau estado pois tem NULLrdbuf. Portanto, definir um estado com exceções habilitadas causa o lançamento de exceção devido ao estado incorreto. Soluções: 1) Use alguma classe (por exemplo std::stringstream) com rdbufset em vez de std::ios. 2) Salve o estado das exceções separadamente na variável local e desative-as antes state.copyfmt; em seguida, restaure a exceção da variável (e faça isso novamente após restaurar o estado do oldStatequal as exceções foram desativadas). 3) Definir rdbufa std::iosgostar deste:struct : std::streambuf {} sbuf; std::ios oldState(&sbuf);
anton_rh
22

Criei uma classe RAII usando o código de exemplo desta resposta. A grande vantagem dessa técnica vem se você tiver vários caminhos de retorno de uma função que define sinalizadores em um iostream. Qualquer que seja o caminho de retorno usado, o destruidor sempre será chamado e os sinalizadores sempre serão redefinidos. Não há chance de esquecer de restaurar os sinalizadores quando a função retornar.

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

Você então o usaria criando uma instância local de IosFlagSaver sempre que quisesse salvar o estado do sinalizador atual. Quando esta instância sai do escopo, o estado do sinalizador será restaurado.

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
fonte
2
Excelente, se alguém jogar, você ainda tem as bandeiras corretas em seu fluxo.
Alexis Wilke
4
Há mais informações sobre o estado do stream além dos sinalizadores.
jww
1
Eu realmente gostaria que o C ++ pudesse tentar / finalmente. Este é um excelente exemplo onde RAII funciona, mas finalmente teria sido mais simples.
Trade-Ideas Philip de
2
Se o seu projeto é pelo menos um pouco lógico, você tem Boost e ele vem com proteções de estado para essa finalidade.
Jan Hudec
9

Com algumas modificações para tornar a saída mais legível:

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

Você pode criar outro wrapper em torno do 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"
}

Em uma função:

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

Claro, se o desempenho for um problema, isso é um pouco mais caro porque está copiando o iosobjeto inteiro (mas não o buffer), incluindo algumas coisas que você está pagando, mas improvável de usar, como o local.

Caso contrário, acho que se você for usar .flags(), é melhor ser consistente e usar .setf()também do que a <<sintaxe (pura questão 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 outros disseram, você pode colocar o acima (e .precision()e .fill(), mas normalmente não o local e as coisas relacionadas a palavras que geralmente não serão modificadas e são mais pesadas) em uma classe por conveniência e para torná-la segura para exceções; o construtor deve aceitar std::ios&.

n.caillou
fonte
Bom ponto [+], mas é claro que lembra de usar std::stringstreampara a parte de formatação como Mark Sherred apontou .
Wolf
@Wolf, não tenho certeza se entendi. Um std::stringstream é um std:ostream, exceto que o uso de um introduz um buffer intermediário extra.
n.caillou
É claro que ambas são abordagens válidas para a formatação de saída, ambas introduzem um objeto stream, o que você descreve é ​​novo para mim. Tenho que pensar sobre prós e contras agora. No entanto, uma pergunta inspiradora com respostas esclarecedoras ... (quero dizer, a variante da cópia do stream)
Wolf
1
Você não pode copiar um fluxo, porque copiar buffers geralmente não faz sentido (por exemplo, stdout). No entanto, você pode ter vários objetos de fluxo para o mesmo buffer, que é o que esta resposta se propõe a fazer. Considerando que um std:stringstreamcriará seu próprio independente std:stringbuf(um std::streambufderivado), que então precisa ser derramado emstd::cout.rdbuf()
n.caillou
Obrigado pelo esclarecimento.
Wolf
0

Eu gostaria de generalizar a resposta de qbert220 um pouco:

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

Isso deve funcionar para fluxos de entrada e outros também.

PS: Eu gostaria de fazer isso simplesmente um comentário para a resposta acima, stackoverflow, entretanto, não me permite fazer isso devido à falta de reputação. Portanto, faça-me bagunçar as respostas aqui em vez de um simples comentário ...

J. Wilde
fonte