Quali manipolatori iomanip sono "appiccicosi"?


140

Di recente ho avuto un problema a crearne uno a stringstreamcausa del fatto che supponevo erroneamente che std::setw()avrebbe influenzato il flusso di stringhe per ogni inserimento, fino a quando non l'ho modificato esplicitamente. Tuttavia, è sempre non impostato dopo l'inserimento.

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

Quindi, ho una serie di domande:

  • Perché è setw()così?
  • Ci sono altri manipolatori in questo modo?
  • C'è una differenza nel comportamento tra std::ios_base::width()e std::setw()?
  • C'è infine un riferimento online che documenta chiaramente questo comportamento? La documentazione del mio fornitore (MS Visual Studio 2005) non sembra mostrarlo chiaramente.

Risposte:


87

Note importanti dai commenti seguenti:

Di Martin:

@Chareles: Quindi con questo requisito tutti i manipolatori sono appiccicosi. Tranne setw che sembra essere ripristinato dopo l'uso.

Di Charles:

Esattamente! e l'unica ragione per cui setw sembra comportarsi diversamente è perché ci sono requisiti sulle operazioni di output formattate per esplicitamente .width (0) il flusso di output.

Di seguito è la discussione che porta alla conclusione di cui sopra:


Guardando il codice i seguenti manipolatori restituiscono un oggetto anziché un flusso:

setiosflags
resetiosflags
setbase
setfill
setprecision
setw

Questa è una tecnica comune per applicare un'operazione solo all'oggetto successivo che viene applicato al flusso. Purtroppo questo non impedisce loro di essere appiccicosi. I test indicano che tutti tranne quelli setwsono appiccicosi.

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

Tutti gli altri manipolatori restituiscono un oggetto stream. Pertanto, tutte le informazioni sullo stato che cambiano devono essere registrate nell'oggetto stream ed è quindi permanente (fino a quando un altro manipolatore non cambia lo stato). Quindi i seguenti manipolatori devono essere manipolatori appiccicosi .

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

dec/ hex/ oct

fixed/ scientific

internal/ left/ right

Questi manipolatori eseguono effettivamente un'operazione sul flusso stesso anziché sull'oggetto flusso (sebbene tecnicamente il flusso faccia parte dello stato degli oggetti flusso). Ma non credo che influenzino qualsiasi altra parte dello stato degli oggetti stream.

ws/ endl/ ends/ flush

La conclusione è che setw sembra essere l'unico manipolatore della mia versione che non è appiccicoso.

Per Charles un semplice trucco per influire solo sull'elemento successivo nella catena:
Ecco un esempio di come un oggetto può essere usato per cambiare temporaneamente lo stato e poi rimetterlo indietro usando un oggetto:

#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

Bel cheat sheet. Aggiungi un riferimento a da dove provengono le informazioni e sarebbe una risposta perfetta.
Mark Ransom,

1
Tuttavia, posso verificare che setfill () sia in effetti 'appiccicoso' sebbene restituisca un oggetto. Quindi penso che questa risposta non sia corretta.
John K,

2
Gli oggetti che restituiscono un flusso devono essere appiccicosi, mentre quelli che restituiscono un oggetto possono essere appiccicosi ma non sono richiesti. Aggiornerò la risposta con le informazioni di John.
Martin York,

1
Non sono sicuro di aver capito il tuo ragionamento. Tutti i manipolatori che accettano parametri sono implementati come funzioni libere che restituiscono un oggetto non specificato che agisce su un flusso quando quell'oggetto viene inserito nel flusso poiché questo è l'unico (?) Modo per preservare la sintassi di inserimento con i parametri. Ad ogni modo, l'appropriato operator<<per il manipolatore assicura che lo stato del flusso sia cambiato in un certo modo. Nessuno dei due moduli crea alcun tipo di sentinella di stato. È solo il comportamento della successiva operazione di inserimento formattata che determina quale parte dello stato viene reimpostata, se presente.
CB Bailey

3
Esattamente! e l'unico motivo che setwsembra comportarsi diversamente è perché ci sono requisiti sulle operazioni di output formattate per esplicitamente .width(0)il flusso di output.
CB Bailey,

31

Il motivo che widthnon sembra essere "appiccicoso" è che certe operazioni possono essere chiamate .width(0)su un flusso di output. Quelli sono:

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]: tutti i do_putsovraccarichi per il num_putmodello. Questi sono usati da sovraccarichi di operator<<prendere un basic_ostreame un tipo numerico incorporato.

22.2.6.2.2 [lib.locale.money.put.virtuals]: tutti i do_putsovraccarichi per il money_putmodello.

27.6.2.5.4 [lib.ostream.inserters.character]: sovraccarichi di operator<<prendere uno basic_ostreame uno dei tipi di caratteri dell'istanza di basic_ostream o char, firmati charo unsigned charo puntatori a matrici di questi tipi di caratteri.

Ad essere onesti, non sono sicuro della logica di ciò, ma nessun altro stato ostreamdovrebbe essere ripristinato da funzioni di output formattate. Naturalmente, cose come badbite failbitpossono essere impostate se si verifica un errore nell'operazione di output, ma ciò dovrebbe essere previsto.

L'unica ragione che mi viene in mente per ripristinare la larghezza è che potrebbe essere sorprendente se, quando si tenta di emettere alcuni campi delimitati, i delimitatori sono stati riempiti.

Per esempio

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

"   4.5     |   3.6      \n"

Per "correggere" ciò richiederebbe:

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

mentre con una larghezza di ripristino, l'output desiderato può essere generato con il più breve:

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

6

setw()influisce solo sull'inserimento successivo. Questo è solo il modo in cui setw()si comporta. Il comportamento di setw()è lo stesso di ios_base::width(). Ho ricevuto le mie setw()informazioni da cplusplus.com .

Puoi trovare un elenco completo dei manipolatori qui . Da quel link, tutti i flag di flusso dovrebbero dire impostati fino a quando non vengono modificati da un altro manipolatore. Una nota a proposito del left, righte internalmanipolatori: Sono come le altre bandiere e non persistono fino alla successiva modifica. Tuttavia, hanno effetto solo quando è impostata la larghezza del flusso e la larghezza deve essere impostata su ogni riga. Quindi, per esempio

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

ti darei

>     a
>     b
>     c

ma

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

ti darei

>     a
>b
>c

I manipolatori di input e output non sono appiccicosi e si verificano solo una volta dove vengono utilizzati. I manipolatori parametrizzati sono diversi, ecco una breve descrizione di ciascuno:

setiosflagsti permette di impostare manualmente i flag, un elenco dei quali può essere trovato qui , quindi è appiccicoso.

resetiosflagssi comporta in modo simile setiosflagstranne se disinserisce i flag specificati.

setbase imposta la base di numeri interi inseriti nel flusso (quindi 17 nella base 16 sarebbe "11", e nella base 2 sarebbe "10001").

setfillimposta il carattere di riempimento da inserire nello stream quando setwviene utilizzato.

setprecision imposta la precisione decimale da utilizzare quando si inseriscono valori in virgola mobile.

setw rende solo l'inserzione successiva la larghezza specificata compilando il carattere specificato in setfill


Bene, la maggior parte di loro sta solo mettendo le bandiere, quindi quelle sono "appiccicose". setw () sembra essere l'unico che influisce su un solo inserimento. Puoi trovare ulteriori dettagli per ciascuno di essi su cplusplus.com/reference/iostream/manipulators
David Brown,

Beh, std::hexanche non è appiccicoso e, ovviamente, std::flusho std::setiosflagsnon sono neanche appiccicosi. Quindi non penso sia così semplice.
sbi,

Solo testando hex e setiosflags (), entrambi sembrano essere appiccicosi (entrambi semplicemente impostano flag che persistono per quel flusso fino a quando non li cambiate).
David Brown,

Sì, la pagina web che affermava std::hexdi non essere appiccicosa era sbagliata - l'ho appena scoperto anche io. I flag di flusso, tuttavia, potrebbero cambiare anche se non si inserisce di std::setiosflagsnuovo uno , quindi si potrebbe vedere questo come non appiccicoso. Inoltre, std::wsnon è neanche appiccicoso. Quindi è non è che facile.
sbi,

Hai fatto un certo sforzo per migliorare la tua risposta. +1
sbi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.