Które manipulatory Iomanip są „lepkie”?

140

Niedawno miałem problem z utworzeniem pliku stringstream, ponieważ błędnie założyłem, std::setw()że wpłynie to na strumień ciągu dla każdego wstawienia, dopóki nie zmienię go wyraźnie. Jednak po włożeniu jest zawsze rozbrojony.

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

Mam więc kilka pytań:

  • Dlaczego tak jest setw()?
  • Czy są tacy manipulatorzy?
  • Czy istnieje różnica w zachowaniu między std::ios_base::width()i std::setw()?
  • Wreszcie, czy istnieje odniesienie online, które jasno dokumentuje to zachowanie? Dokumentacja mojego dostawcy (MS Visual Studio 2005) nie wydaje się tego jasno pokazywać.
John K.
źródło
Runda robocza jest tutaj: stackoverflow.com/a/37495361/984471
Manohar Reddy Poreddy

Odpowiedzi:

87

Ważne uwagi z poniższych komentarzy:

Martin:

@Chareles: Zatem zgodnie z tym wymaganiem wszystkie manipulatory są lepkie. Z wyjątkiem setw, który wydaje się być resetowany po użyciu.

Charles:

Dokładnie! a jedynym powodem, dla którego setw wydaje się zachowywać inaczej, jest to, że istnieją wymagania dotyczące sformatowanych operacji wyjściowych, aby jawnie jawnie podawać .width (0) strumień wyjściowy.

Poniżej znajduje się dyskusja prowadząca do powyższego wniosku:


Patrząc na kod, następujące manipulatory zwracają obiekt, a nie strumień:

setiosflags
resetiosflags
setbase
setfill
setprecision
setw

Jest to powszechna technika stosowania operacji tylko do następnego obiektu, który jest stosowany do strumienia. Niestety nie wyklucza to ich lepkości. Testy wskazują, że wszystkie z wyjątkiem setwsą lepkie.

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

Wszystkie inne manipulatory zwracają obiekt strumienia. W związku z tym wszelkie informacje o stanie, które zmieniają, muszą być zapisane w obiekcie strumienia i dlatego są trwałe (do czasu zmiany stanu przez innego manipulatora). Zatem następujące manipulatory muszą być manipulatorami Sticky .

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

dec/ hex/ oct

fixed/ scientific

internal/ left/ right

Te manipulatory w rzeczywistości wykonują operację na samym strumieniu, a nie na obiekcie strumienia (chociaż technicznie strumień jest częścią stanu obiektów strumienia). Ale nie wierzę, że wpływają one na jakąkolwiek inną część stanu obiektów strumienia.

ws/ endl/ ends/ flush

Wniosek jest taki, że setw wydaje się być jedynym manipulatorem w mojej wersji, który nie jest lepki.

Dla Charlesa prosta sztuczka polegająca na wpłynięciu tylko na następny element w łańcuchu:
Oto przykład, w jaki sposób obiekt może być użyty do czasowej zmiany stanu, a następnie przywrócenia go za pomocą obiektu:

#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
źródło
Niezła ściągawka. Dodaj odniesienie do źródła informacji, a to byłaby doskonała odpowiedź.
Mark Ransom
1
Mogę jednak sprawdzić, czy setfill () jest w rzeczywistości „lepki”, chociaż zwraca obiekt. Więc myślę, że ta odpowiedź nie jest poprawna.
John K
2
Obiekty, które zwracają strumień, muszą być lepkie, podczas gdy te, które zwracają obiekt, mogą być lepkie, ale nie jest to wymagane. Zaktualizuję odpowiedź o informacje o Johnie.
Martin York
1
Nie jestem pewien, czy rozumiem twoje rozumowanie. Wszystkie manipulatory pobierające parametry są implementowane jako funkcje swobodne zwracające nieokreślony obiekt, który działa na strumieniu, gdy ten obiekt jest wstawiany do strumienia, ponieważ jest to jedyny (?) Sposób zachowania składni wstawiania z parametrami. Tak czy inaczej, odpowiedni operator<<dla manipulatora zapewnia, że ​​stan strumienia zmienia się w określony sposób. Żaden z formularzy nie ustanawia żadnego rodzaju strażnika państwowego. Tylko zachowanie następnej sformatowanej operacji wstawiania określa, która część stanu jest resetowana, jeśli w ogóle.
CB Bailey
3
Dokładnie! a jedynym powodem, dla którego setwwydaje się, że zachowuje się inaczej, jest to, że istnieją wymagania dotyczące sformatowanych operacji wyjściowych, które jawnie dotyczą .width(0)strumienia wyjściowego.
CB Bailey
31

Przyczyną, widthktóra nie wydaje się być „lepka”, jest to, że pewne operacje wywołują .width(0)strumień wyjściowy. To są:

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]: Wszystkie do_putprzeciążenia num_putszablonu. Są one używane przez przeciążenia operator<<przyjmowania a basic_ostreami wbudowanego typu liczbowego.

22.2.6.2.2 [lib.locale.money.put.virtuals]: Wszystkie do_putprzeciążenia money_putszablonu.

27.6.2.5.4 [lib.ostream.inserters.character]: Przeciążenie związane z operator<<pobieraniem a basic_ostreami jednego typu char instancji basic_ostream lub char, signed charlub unsigned charlub wskaźniki do tablic tych typów char.

Szczerze mówiąc, nie jestem pewien, dlaczego tak jest, ale żadne inne stany nie ostreampowinny być resetowane przez sformatowane funkcje wyjściowe. Oczywiście takie rzeczy jak badbiti failbitmogą być ustawione, jeśli wystąpi błąd w działaniu wyjścia, ale należy się tego spodziewać.

Jedynym powodem, dla którego przychodzi mi do głowy, aby zresetować szerokość, jest to, że może być zaskakujące, jeśli podczas próby wyprowadzenia niektórych pól rozdzielanych ograniczniki zostały wypełnione.

Na przykład

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

"   4.5     |   3.6      \n"

Aby to „poprawić”, należałoby:

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

podczas gdy przy zerowaniu szerokości, pożądane wyjście można wygenerować z krótszymi:

std::cout << std::setw(6) << 4.5 << '|' << std::setw(6) << 3.6 << '\n';
CB Bailey
źródło
6

setw()wpływa tylko na następne wstawienie. Po prostu tak się setw()zachowuje. Zachowanie setw()jest takie samo jak ios_base::width(). Mam setw()informacje z cplusplus.com .

Pełną listę manipulatorów znajdziesz tutaj . Z tego łącza wszystkie flagi strumienia powinny mówić ustawione do czasu zmiany przez innego manipulatora. Jedna uwaga na temat left, righti internalmanipulatory są jak inne flagi i nie utrzymują się aż zmieniło. Jednak mają one wpływ tylko wtedy, gdy ustawiona jest szerokość strumienia, a szerokość musi być ustawiana w każdym wierszu. Na przykład

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

dałby ci

>     a
>     b
>     c

ale

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

dałby ci

>     a
>b
>c

Manipulatory wejścia i wyjścia nie są lepkie i pojawiają się tylko raz, gdy są używane. Sparametryzowane manipulatory są różne, oto krótki opis każdego z nich:

setiosflagspozwala ręcznie ustawić flagi, których listę można znaleźć tutaj , więc jest lepka.

resetiosflagszachowuje się podobnie do setiosflagsz wyjątkiem tego, że usuwa określone flagi.

setbase ustawia podstawę liczb całkowitych wstawionych do strumienia (więc 17 w bazie 16 będzie równe „11”, a podstawą 2 będzie „10001”).

setfillustawia znak wypełnienia do wstawienia do strumienia, gdy setwjest używany.

setprecision ustawia dokładność dziesiętną, która ma być używana podczas wstawiania wartości zmiennoprzecinkowych.

setw sprawia, że ​​tylko następne wstawienie ma określoną szerokość, wypełniając znak określony w setfill

David Brown
źródło
Cóż, większość z nich tylko ustawia flagi, więc te są „lepkie”. setw () wydaje się być jedyną, która wpływa tylko na jedno wstawienie. Więcej szczegółowych informacji na temat każdego z nich można znaleźć na stronie cplusplus.com/reference/iostream/manipulators
David Brown,
Dobrze std::hexteż nie jest lepki i, oczywiście, std::flushczy std::setiosflagsnie są lepkie albo. Więc nie sądzę, że to takie proste.
sbi
Po prostu testując hex i setiosflags (), oba wydają się być lepkie (oba po prostu ustawiają flagi, które pozostają dla tego strumienia, dopóki ich nie zmienisz).
David Brown
Tak, strona internetowa, która twierdziła, że std::hexnie jest lepka, była błędna - ja też się o tym dowiedziałem. Flagi strumienia mogą się jednak zmienić, nawet jeśli nie wstawisz std::setiosflagsponownie znaku, więc można to uznać za nieprzywierające. Nie std::wsjest też lepki. Nie jest to więc takie proste.
sbi
Włożyłeś sporo wysiłku w poprawę swojej odpowiedzi. +1
sbi