Welche Iomanip-Manipulatoren sind "klebrig"?

140

Ich hatte kürzlich ein Problem beim Erstellen eines stringstream, da ich fälschlicherweise angenommen habe, dass std::setw()dies den Stringstream bei jeder Einfügung beeinflussen würde, bis ich ihn explizit geändert habe. Es wird jedoch nach dem Einfügen immer deaktiviert.

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

Ich habe also eine Reihe von Fragen:

  • Warum ist setw()das so?
  • Sind andere Manipulatoren so?
  • Gibt es einen Unterschied im Verhalten zwischen std::ios_base::width()und std::setw()?
  • Gibt es schließlich eine Online-Referenz, die dieses Verhalten klar dokumentiert? Meine Herstellerdokumentation (MS Visual Studio 2005) scheint dies nicht klar zu zeigen.
John K.
quelle
Eine Arbeitsrunde ist hier: stackoverflow.com/a/37495361/984471
Manohar Reddy Poreddy

Antworten:

87

Wichtige Hinweise aus den Kommentaren unten:

Von Martin:

@ Charles: Dann sind durch diese Anforderung alle Manipulatoren klebrig. Außer setw, das nach Gebrauch zurückgesetzt zu werden scheint.

Von Charles:

Genau! und der einzige Grund, warum sich setw anders zu verhalten scheint, ist, dass es Anforderungen an formatierte Ausgabeoperationen gibt, um den Ausgabestream explizit mit .width (0) zu versehen.

Das Folgende ist die Diskussion, die zu der obigen Schlussfolgerung führt:


Wenn Sie sich den Code ansehen, geben die folgenden Manipulatoren eher ein Objekt als einen Stream zurück:

setiosflags
resetiosflags
setbase
setfill
setprecision
setw

Dies ist eine übliche Technik, um eine Operation nur auf das nächste Objekt anzuwenden, das auf den Stream angewendet wird. Leider schließt dies nicht aus, dass sie klebrig sind. Tests zeigen, dass alle außer setwklebrig sind.

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

Alle anderen Manipulatoren geben ein Stream-Objekt zurück. Daher müssen alle Statusinformationen, die sie ändern, im Stream-Objekt aufgezeichnet werden und sind somit permanent (bis ein anderer Manipulator den Status ändert). Daher müssen die folgenden Manipulatoren Sticky- Manipulatoren sein.

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

dec/ hex/ oct

fixed/ scientific

internal/ left/ right

Diese Manipulatoren führen tatsächlich eine Operation für den Stream selbst und nicht für das Stream-Objekt aus (obwohl der Stream technisch gesehen Teil des Status der Stream-Objekte ist). Ich glaube jedoch nicht, dass sie einen anderen Teil des Status der Stream-Objekte beeinflussen.

ws/ endl/ ends/ flush

Die Schlussfolgerung ist, dass setw der einzige Manipulator in meiner Version zu sein scheint, der nicht klebrig ist.

Für Charles ein einfacher Trick, um nur das nächste Element in der Kette zu beeinflussen:
Hier ist ein Beispiel, wie ein Objekt verwendet werden kann, um den Status vorübergehend zu ändern und ihn dann mithilfe eines Objekts zurückzusetzen:

#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
quelle
Schöner Spickzettel. Fügen Sie einen Verweis darauf hinzu, woher die Informationen stammen, und es wäre eine perfekte Antwort.
Mark Ransom
1
Ich kann jedoch überprüfen, ob setfill () tatsächlich "klebrig" ist, obwohl es ein Objekt zurückgibt. Ich denke, diese Antwort ist nicht richtig.
John K
2
Objekte, die einen Stream zurückgeben, müssen klebrig sein, während Objekte, die ein Objekt zurückgeben, klebrig sein können, dies ist jedoch nicht erforderlich. Ich werde die Antwort mit Johns Info aktualisieren.
Martin York
1
Ich bin mir nicht sicher, ob ich Ihre Argumentation verstehe. Alle Manipulatoren, die Parameter verwenden, werden als freie Funktionen implementiert, die ein nicht angegebenes Objekt zurückgeben, das auf einen Stream einwirkt, wenn dieses Objekt in den Stream eingefügt wird, da dies die einzige (?) Möglichkeit ist, die Einfügesyntax mit Parametern beizubehalten. In jedem Fall stellt der operator<<für den Manipulator geeignete Wert sicher, dass der Status des Streams auf eine bestimmte Weise geändert wird. Keines der beiden Formulare richtet einen staatlichen Wachposten ein. Nur das Verhalten der nächsten formatierten Einfügeoperation bestimmt, welcher Teil des Status gegebenenfalls zurückgesetzt wird.
CB Bailey
3
Genau! und der einzige Grund, setwder sich anders zu verhalten scheint, ist, dass es Anforderungen an formatierte Ausgabeoperationen gibt, um .width(0)den Ausgabestream explizit anzuzeigen.
CB Bailey
31

Der Grund, widthder nicht "klebrig" zu sein scheint, ist, dass bestimmte Vorgänge garantiert .width(0)einen Ausgabestream aufrufen . Jene sind:

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]: Alle do_putÜberladungen für die num_putVorlage. Diese werden bei Überlastungen operator<<mit einem basic_ostreamund einem eingebauten numerischen Typ verwendet.

22.2.6.2.2 [lib.locale.money.put.virtuals]: Alle do_putÜberladungen für die money_putVorlage.

27.6.2.5.4 [lib.ostream.inserters.character]: Überladungen von operator<<a basic_ostreamund eins des char-Typs der basic_ostream-Instanziierung oder char, signiert charoder unsigned charoder Zeiger auf Arrays dieser char-Typen.

Um ehrlich zu sein, bin ich mir der Gründe dafür nicht sicher, aber keine anderen Zustände von ostreamsollten durch formatierte Ausgabefunktionen zurückgesetzt werden. Natürlich können Dinge wie badbitund eingestellt failbitwerden, wenn ein Fehler in der Ausgabeoperation auftritt, aber das sollte erwartet werden.

Der einzige Grund, den ich mir für das Zurücksetzen der Breite vorstellen kann, ist, dass es möglicherweise überraschend ist, wenn beim Versuch, einige begrenzte Felder auszugeben, Ihre Trennzeichen aufgefüllt wurden.

Z.B

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

"   4.5     |   3.6      \n"

Um dies zu "korrigieren", müsste man:

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

Bei einer Rücksetzbreite kann die gewünschte Ausgabe mit der kürzeren erzeugt werden:

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

setw()wirkt sich nur auf die nächste Einfügung aus. So setw()verhält es sich einfach . Das Verhalten von setw()ist das gleiche wie ios_base::width(). Ich habe meine setw()Informationen von cplusplus.com erhalten .

Eine vollständige Liste der Manipulatoren finden Sie hier . Von diesem Link aus sollten alle Stream-Flags gesetzt sein, bis sie von einem anderen Manipulator geändert werden. Eine Anmerkung über das left, rightund internalManipulatoren: Sie sind wie die anderen Flaggen und sie bestehen bleiben , bis sie geändert. Sie wirken sich jedoch nur aus, wenn die Breite des Streams festgelegt ist und die Breite für jede Zeile festgelegt werden muss. So zum Beispiel

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

würde dir geben

>     a
>     b
>     c

aber

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

würde dir geben

>     a
>b
>c

Die Eingabe- und Ausgabemanipulatoren sind nicht klebrig und treten nur einmal dort auf, wo sie verwendet werden. Die parametrisierten Manipulatoren sind jeweils unterschiedlich. Hier eine kurze Beschreibung der einzelnen:

setiosflagsMit dieser Option können Sie Flags manuell setzen, von denen eine Liste hier als Quelle verwendet werden kann.

resetiosflagsverhält sich ähnlich wie, setiosflagsaußer dass die angegebenen Flags deaktiviert werden.

setbase Legt die Basis der in den Stream eingefügten Ganzzahlen fest (also wäre 17 in Basis 16 "11" und in Basis 2 "10001").

setfillLegt das Füllzeichen fest, das bei setwVerwendung in den Stream eingefügt werden soll.

setprecision Legt die Dezimalgenauigkeit fest, die beim Einfügen von Gleitkommawerten verwendet werden soll.

setw macht nur beim nächsten Einfügen die angegebene Breite, indem es mit dem in angegebenen Feld füllt setfill

David Brown
quelle
Nun, die meisten von ihnen setzen nur Flaggen, also sind diese "klebrig". setw () scheint die einzige zu sein, die nur eine Einfügung betrifft. Weitere Einzelheiten
David Brown
Nun std::hexwird auch nicht klebrig und, natürlich, std::flushoder std::setiosflagsnicht klebrig ist entweder. Ich denke also nicht, dass es so einfach ist.
sbi
Wenn Sie nur hex und setiosflags () testen, scheinen beide klebrig zu sein (beide setzen einfach Flags, die für diesen Stream bestehen bleiben, bis Sie sie ändern).
David Brown
Ja, die Webseite, die behauptete std::hex, nicht klebrig zu sein, war falsch - ich habe das auch gerade herausgefunden. Stream-Flags können sich jedoch ändern, selbst wenn Sie keine std::setiosflagserneut einfügen , sodass dies als nicht klebrig angesehen werden kann. Auch std::wsist nicht klebrig. So ist es ist nicht , dass einfach.
sbi
Sie haben einige Anstrengungen unternommen, um Ihre Antwort zu verbessern. +1
sbi