¿Qué manipuladores iomanip son 'pegajosos'?

140

Recientemente tuve un problema para crear un stringstreamdebido al hecho de que asumí incorrectamente std::setw()que afectaría el flujo de cadena para cada inserción, hasta que lo cambié explícitamente. Sin embargo, siempre se desarma después de la inserción.

// 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'

Entonces, tengo una serie de preguntas:

  • ¿Por qué es setw()así?
  • ¿Hay otros manipuladores de esta manera?
  • ¿Hay alguna diferencia en el comportamiento entre std::ios_base::width()y std::setw()?
  • Finalmente, ¿hay una referencia en línea que documente claramente este comportamiento? La documentación de mi proveedor (MS Visual Studio 2005) no parece mostrar esto claramente.
John K
fuente
Una ronda de trabajo está aquí: stackoverflow.com/a/37495361/984471
Manohar Reddy Poreddy

Respuestas:

87

Notas importantes de los comentarios a continuación:

Por Martin:

@Chareles: Entonces, por este requisito, todos los manipuladores son pegajosos. Excepto setw que parece reiniciarse después de su uso.

Por Charles:

¡Exactamente! y la única razón por la que setw parece comportarse de manera diferente es porque hay requisitos en las operaciones de salida formateadas para explícitamente .width (0) el flujo de salida.

La siguiente es la discusión que lleva a la conclusión anterior:


Mirando el código, los siguientes manipuladores devuelven un objeto en lugar de una secuencia:

setiosflags
resetiosflags
setbase
setfill
setprecision
setw

Esta es una técnica común para aplicar una operación solo al siguiente objeto que se aplica a la secuencia. Lamentablemente, esto no impide que sean pegajosos. Las pruebas indican que todas ellas, excepto setwlas adhesivas.

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

Todos los demás manipuladores devuelven un objeto de flujo. Por lo tanto, cualquier información de estado que cambien debe registrarse en el objeto de flujo y, por lo tanto, es permanente (hasta que otro manipulador cambie el estado). Por lo tanto, los siguientes manipuladores deben ser manipuladores fijos .

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

dec/ hex/ oct

fixed/ scientific

internal/ left/ right

Estos manipuladores realmente realizan una operación en el flujo en sí mismo en lugar del objeto del flujo (aunque técnicamente el flujo es parte del estado de los objetos del flujo). Pero no creo que afecten a ninguna otra parte del estado de los objetos de flujo.

ws/ endl/ ends/ flush

La conclusión es que setw parece ser el único manipulador en mi versión que no es pegajoso.

Para Charles, un simple truco para afectar solo el siguiente elemento de la cadena:
Aquí hay un ejemplo de cómo un objeto puede usarse para cambiar temporalmente el estado y luego volver a colocarlo mediante el uso de un 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
fuente
Bonita hoja de trucos. Agregue una referencia de dónde proviene la información, y sería una respuesta perfecta.
Mark Ransom
1
Sin embargo, puedo verificar que setfill () es de hecho 'pegajoso' aunque devuelve un objeto. Entonces creo que esta respuesta no es correcta.
John K
2
Los objetos que devuelven un flujo deben ser permanentes, mientras que los que devuelven un objeto pueden ser permanentes pero no es obligatorio. Actualizaré la respuesta con la información de John.
Martin York
1
No estoy seguro de entender tu razonamiento. Todos los manipuladores que toman parámetros se implementan como funciones libres que devuelven un objeto no especificado que actúa en una secuencia cuando ese objeto se inserta en la secuencia, ya que esta es la única forma (?) De preservar la sintaxis de inserción con parámetros. De cualquier manera, lo apropiado operator<<para el manipulador asegura que el estado del flujo se cambie de cierta manera. Ninguno de los dos formularios configura ningún tipo de centinela estatal. Es solo el comportamiento de la siguiente operación de inserción formateada lo que determina qué parte del estado se restablece, si corresponde.
CB Bailey, el
3
¡Exactamente! y la única razón por la que setwparece comportarse de manera diferente es porque hay requisitos en las operaciones de salida formateadas para explícitamente .width(0)la secuencia de salida.
CB Bailey
31

La razón por la que widthno parece ser "permanente" es que se garantiza que ciertas operaciones invoquen .width(0)una secuencia de salida. Esos son:

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 las do_putsobrecargas de la num_putplantilla. Estos son utilizados por sobrecargas de operator<<tomar un basic_ostreamtipo numérico incorporado.

22.2.6.2.2 [lib.locale.money.put.virtuals]: todas las do_putsobrecargas de la money_putplantilla.

27.6.2.5.4 [lib.ostream.inserters.character]: Sobrecargas de operator<<tomar un basic_ostreamy uno del tipo char de la instanciación basic_ostream o char, firmado charo unsigned charo punteros a matrices de estos tipos char.

Para ser honesto, no estoy seguro de la razón de esto, pero ningún otro estado de un ostreamdebe restablecerse mediante funciones de salida formateadas. Por supuesto, cosas como badbity failbitpueden establecerse si hay una falla en la operación de salida, pero eso debería esperarse.

La única razón por la que puedo pensar para restablecer el ancho es que puede ser sorprendente si, al intentar generar algunos campos delimitados, sus delimitadores se rellenan.

P.ej

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

"   4.5     |   3.6      \n"

Para 'corregir' esto tomaría:

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

mientras que con un ancho de reinicio, la salida deseada se puede generar con el más corto:

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

setw()solo afecta la siguiente inserción. Así es como se setw()comporta. El comportamiento de setw()es el mismo que ios_base::width(). Obtuve mi setw()información de cplusplus.com .

Puede encontrar una lista completa de manipuladores aquí . Desde ese enlace, todos los indicadores de flujo deberían decir establecido hasta que otro manipulador los cambie. Una nota sobre el left, righty internalmanipuladores: Son como las otras banderas y no persisten hasta que se cambie. Sin embargo, solo tienen efecto cuando se establece el ancho de la secuencia, y el ancho debe establecerse en cada línea. Así por ejemplo

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

te daría

>     a
>     b
>     c

pero

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

te daría

>     a
>b
>c

Los manipuladores de entrada y salida no son fijos y solo ocurren una vez donde se usan. Los manipuladores parametrizados son diferentes, aquí hay una breve descripción de cada uno:

setiosflagsle permite configurar banderas manualmente, una lista de las cuales puede encontrar aquí , por lo que es pegajosa.

resetiosflagsse comporta de forma similar a setiosflagsexcepto que desarma los indicadores especificados.

setbase establece la base de los enteros insertados en la secuencia (de modo que 17 en la base 16 sería "11", y en la base 2 sería "10001").

setfillestablece el carácter de relleno para insertar en la secuencia cuando setwse usa.

setprecision establece la precisión decimal que se utilizará al insertar valores de coma flotante.

setw hace que solo la siguiente inserción tenga el ancho especificado rellenando con el carácter especificado en setfill

David Brown
fuente
Bueno, la mayoría de ellos solo establecen banderas, por lo que son "pegajosos". setw () parece ser el único que afecta solo una inserción. Puede encontrar más detalles para cada uno en cplusplus.com/reference/iostream/manipulators
David Brown
Bueno, std::hextampoco es pegajoso y, obviamente, std::flusho std::setiosflagstampoco es pegajoso. Entonces no creo que sea tan simple.
sbi
Simplemente probando hexadecimal y setiosflags (), ambos parecen ser pegajosos (ambos simplemente establecen banderas que persisten para esa secuencia hasta que las cambie).
David Brown
Sí, la página web que decía std::hexno ser pegajosa estaba equivocada, también lo descubrí. Sin embargo, los indicadores de transmisión pueden cambiar incluso si no inserta uno std::setiosflagsnuevamente, por lo que uno podría ver esto como no adhesivo. Además, std::wstampoco es pegajoso. Por lo que es no que fácil.
sbi
Te has esforzado bastante para mejorar tu respuesta. +1
sbi