Quais manipuladores iomanip são 'pegajosos'?

140

Recentemente, tive um problema ao criar um stringstreamdevido ao fato de que assumi incorretamente std::setw()que afetaria o fluxo de seqüência de caracteres para cada inserção, até que eu o alterasse explicitamente. No entanto, é sempre desabilitado após a inserção.

// With timestruct with value of 'Oct 7 9:04 AM'
std::stringstream ss;
ss.fill('0'); ss.setf(ios::right, ios::adjustfield);
ss << setw(2) << timestruct.tm_mday;
ss << timestruct.tm_hour;
ss << timestruct.tm_min;
std::string filingTime = ss.str(); // BAD: '0794'

Então, eu tenho várias perguntas:

  • Por que é setw()assim?
  • Existem outros manipuladores dessa maneira?
  • Existe uma diferença de comportamento entre std::ios_base::width()e std::setw()?
  • Finalmente, existe uma referência online que documenta claramente esse comportamento? A documentação do meu fornecedor (MS Visual Studio 2005) parece não mostrar isso claramente.
John K
fonte
A rodada de trabalho é aqui: stackoverflow.com/a/37495361/984471
Manohar Reddy Poreddy

Respostas:

87

Notas importantes dos comentários abaixo:

Por Martin:

@Chareles: Então, por esse requisito, todos os manipuladores são pegajosos. Exceto setw, que parece ser redefinido após o uso.

Por Charles:

Exatamente! e o único motivo pelo qual o setw parece se comportar de maneira diferente é porque existem requisitos nas operações de saída formatadas para explicitamente .width (0) o fluxo de saída.

A seguir, é apresentada a discussão que leva à conclusão acima:


Observando o código, os seguintes manipuladores retornam um objeto em vez de um fluxo:

setiosflags
resetiosflags
setbase
setfill
setprecision
setw

Essa é uma técnica comum para aplicar uma operação apenas ao próximo objeto aplicado ao fluxo. Infelizmente, isso não os impede de serem pegajosos. Os testes indicam que todos eles, exceto setwsão pegajosos.

setiosflags:  Sticky
resetiosflags:Sticky
setbase:      Sticky
setfill:      Sticky
setprecision: Sticky

Todos os outros manipuladores retornam um objeto de fluxo. Portanto, qualquer informação de estado alterada deve ser registrada no objeto de fluxo e, portanto, permanente (até que outro manipulador altere o estado). Assim, os seguintes manipuladores devem ser manipuladores pegajosos .

[no]boolalpha
[no]showbase
[no]showpoint
[no]showpos
[no]skipws
[no]unitbuf
[no]uppercase

dec/ hex/ oct

fixed/ scientific

internal/ left/ right

Esses manipuladores realmente executam uma operação no próprio fluxo, e não no objeto do fluxo (embora tecnicamente o fluxo faça parte do estado dos objetos do fluxo). Mas não acredito que eles afetem qualquer outra parte do estado dos objetos de fluxo.

ws/ endl/ ends/ flush

A conclusão é que o setw parece ser o único manipulador da minha versão que não é pegajoso.

Para Charles, um truque simples para afetar apenas o próximo item da cadeia:
Aqui está um exemplo de como um objeto pode ser usado para alterar temporariamente o estado e depois devolvê-lo pelo uso de um objeto:

#include <iostream>
#include <iomanip>

// Private object constructed by the format object PutSquareBracket
struct SquareBracktAroundNextItem
{
    SquareBracktAroundNextItem(std::ostream& str)
        :m_str(str)
    {}
    std::ostream& m_str;
};

// New Format Object
struct PutSquareBracket
{};

// Format object passed to stream.
// All it does is return an object that can maintain state away from the
// stream object (so that it is not STICKY)
SquareBracktAroundNextItem operator<<(std::ostream& str,PutSquareBracket const& data)
{
    return SquareBracktAroundNextItem(str);
}

// The Non Sticky formatting.
// Here we temporariy set formating to fixed with a precision of 10.
// After the next value is printed we return the stream to the original state
// Then return the stream for normal processing.
template<typename T>
std::ostream& operator<<(SquareBracktAroundNextItem const& bracket,T const& data)
{
    std::ios_base::fmtflags flags               = bracket.m_str.flags();
    std::streamsize         currentPrecision    = bracket.m_str.precision();

    bracket.m_str << '[' << std::fixed << std::setprecision(10) << data << std::setprecision(currentPrecision) << ']';

    bracket.m_str.flags(flags);

    return bracket.m_str;
}


int main()
{

    std::cout << 5.34 << "\n"                        // Before 
              << PutSquareBracket() << 5.34 << "\n"  // Temp change settings.
              << 5.34 << "\n";                       // After
}


> ./a.out 
5.34
[5.3400000000]
5.34
Martin York
fonte
Nice cheat sheet. Adicione uma referência à origem das informações e seria uma resposta perfeita.
Mark Ransom
1
No entanto, posso verificar se setfill () é de fato 'pegajoso', embora retorne um objeto. Então, acho que essa resposta não está correta.
John K
2
Objetos que retornam um fluxo devem ser fixos, enquanto aqueles que retornam um objeto podem ser fixos, mas não são necessários. Atualizarei a resposta com as informações de John.
Martin Iorque
1
Não sei se entendi seu raciocínio. Todos os manipuladores que usam parâmetros são implementados como funções livres, retornando um objeto não especificado que atua em um fluxo quando esse objeto é inserido no fluxo, pois esta é a única maneira (?) De preservar a sintaxe de inserção com parâmetros. De qualquer forma, o apropriado operator<<para o manipulador garante que o estado do fluxo seja alterado de uma certa maneira. Nenhum dos formulários configura qualquer tipo de sentinela estadual. É apenas o comportamento da próxima operação de inserção formatada que determina qual parte do estado será redefinida, se houver.
55538 CB Bailey
3
Exatamente! e o único motivo que setwparece se comportar de maneira diferente é porque existem requisitos nas operações de saída formatadas para explicitamente .width(0)o fluxo de saída.
CB Bailey
31

O motivo que widthnão parece ser "persistente" é que certas operações têm garantia de chamar .width(0)um fluxo de saída. Esses são:

21.3.7.9 [lib.string.io]:

template<class charT, class traits, class Allocator>
  basic_ostream<charT, traits>&
    operator<<(basic_ostream<charT, traits>& os,
               const basic_string<charT,traits,Allocator>& str);

22.2.2.2.2 [lib.facet.num.put.virtuals]: todas as do_putsobrecargas para o num_putmodelo. Estes são utilizados por sobrecargas de operator<<tomar uma basic_ostreame construído em um tipo numérico.

22.2.6.2.2 [lib.locale.money.put.virtuals]: todas as do_putsobrecargas para o money_putmodelo.

27.6.2.5.4 [lib.ostream.inserters.character]: sobrecargas de operator<<pegar um basic_ostreame um do tipo de caractere da instanciação basic_ostream ou char, assinado charou unsigned charou ponteiros para matrizes desses tipos de caracteres.

Para ser sincero, não tenho certeza da justificativa para isso, mas nenhum outro estado de um ostreamdeve ser redefinido pelas funções de saída formatadas. Obviamente, coisas como badbite failbitpodem ser definidas se houver uma falha na operação de saída, mas isso deve ser esperado.

A única razão pela qual posso pensar para redefinir a largura é que pode ser surpreendente se, ao tentar gerar alguns campos delimitados, seus delimitadores forem preenchidos.

Por exemplo

std::cout << std::setw(6) << 4.5 << '|' << 3.6 << '\n';

"   4.5     |   3.6      \n"

Para 'corrigir' isso levaria:

std::cout << std::setw(6) << 4.5 << std::setw(0) << '|' << std::setw(6) << 3.6 << std::setw(0) << '\n';

enquanto que com uma largura de redefinição, a saída desejada pode ser gerada com o menor:

std::cout << std::setw(6) << 4.5 << '|' << std::setw(6) << 3.6 << '\n';
CB Bailey
fonte
6

setw()afeta apenas a próxima inserção. É assim que setw()se comporta. O comportamento de setw()é o mesmo que ios_base::width(). Eu obtive minhas setw()informações em cplusplus.com .

Você pode encontrar uma lista completa de manipuladores aqui . A partir desse link, todos os sinalizadores de fluxo devem ser definidos até serem alterados por outro manipulador. Uma nota sobre a left, righte internalmanipuladores: Eles são como as outras bandeiras e que persistem até que sejam alteradas. No entanto, eles só têm efeito quando a largura do fluxo é definida e a largura deve ser definida em todas as linhas. Então, por exemplo

cout.width(6);
cout << right << "a" << endl;
cout.width(6);
cout << "b" << endl;
cout.width(6);
cout << "c" << endl;

te daria

>     a
>     b
>     c

mas

cout.width(6);
cout << right << "a" << endl;
cout << "b" << endl;
cout << "c" << endl;

te daria

>     a
>b
>c

Os manipuladores de entrada e saída não são pegajosos e ocorrem apenas uma vez onde são usados. Os manipuladores parametrizados são diferentes, aqui está uma breve descrição de cada um:

setiosflagspermite que você defina manualmente sinalizadores, cuja lista pode ser encontrada aqui , para que fique adesiva.

resetiosflagscomporta-se de maneira semelhante a, setiosflagsexceto que desativa os sinalizadores especificados.

setbase define a base de números inteiros inseridos no fluxo (então 17 na base 16 seria "11" e na base 2 seria "10001").

setfilldefine o caractere de preenchimento para inserir no fluxo quando setwé usado.

setprecision define a precisão decimal a ser usada ao inserir valores de ponto flutuante.

setw torna apenas a próxima inserção a largura especificada preenchendo o caractere especificado em setfill

David Brown
fonte
Bem, a maioria deles está apenas definindo sinalizadores, então esses são "pegajosos". setw () parece ser o único que afeta apenas uma inserção. Você pode descobrir mais detalhes para cada um em cplusplus.com/reference/iostream/manipulators
David Brown
Bem, std::hextambém não é pegajoso e, obviamente, std::flushou std::setiosflagstambém não é pegajoso. Então, eu não acho tão simples assim.
S7
Apenas testando hex e setiosflags (), ambos parecem ser pegajosos (ambos simplesmente definem sinalizadores que persistem para esse fluxo até você alterá-los).
7119 David Brown
Sim, a página da web que alegava std::hexnão ser pegajosa estava errada - eu também descobri isso também. Os sinalizadores de fluxo, no entanto, podem ser alterados, mesmo que você não insira um std::setiosflagsnovo, para que você possa ver isso como não aderente. Além disso, também std::wsnão é pegajoso. Portanto, não é assim tão fácil.
S7
Você se esforçou bastante para melhorar sua resposta. +1
sbi 9/10/09