Какие манипуляторы iomanip являются "липкими"?

Недавно у меня возникла проблема с созданием строкового потока из-за того, что я неправильно предположил, что std:: setw() повлияет на строковый поток для каждой вставки, пока я не изменю его явно. Тем не менее, он всегда отключается после вставки.

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

Итак, у меня есть ряд вопросов:

  • Почему setw() таким образом?
  • Есть ли другие манипуляторы таким образом?
  • Есть ли разница в поведении между std:: ios_base:: width() и std:: setw()?
  • Наконец, есть онлайн-ссылка, которая четко документирует это поведение? Моя документация поставщика (MS Visual Studio 2005), похоже, не показывает это явно.

Ответ 1

Важные замечания из комментариев ниже:

Мартин:

@Chareles: Тогда по этому требованию все манипуляторы являются липкими. За исключением setw, который, кажется, reset после использования.

Чарльз:

Точно! и единственная причина, по которой setw, по-видимому, ведет себя по-другому, состоит в том, что требования к форматированным операциям вывода явно выражаются .width(0) выходной поток.

Ниже приводится обсуждение, которое приводит к вышесказанному выводу:


Глядя на код, следующие манипуляторы возвращают объект, а не поток:

setiosflags
resetiosflags
setbase
setfill
setprecision
setw

Это общий метод применения операции только к следующему объекту, который применяется к потоку. К сожалению, это не мешает им быть липкими. Тесты показывают, что все из них, кроме setw, являются липкими.

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

Все остальные манипуляторы возвращают объект потока. Таким образом, любая информация о состоянии, которую они изменяют, должна записываться в объект потока и, таким образом, постоянна (пока другой манипулятор не изменит состояние). Таким образом, следующие манипуляторы должны быть липкими манипуляторами.

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

dec/ hex/ oct

fixed/ scientific

internal/ left/ right

Эти манипуляторы фактически выполняют операцию над самим потоком, а не объектом потока (хотя технически поток является частью состояния объектов потока). Но я не думаю, что они влияют на любую другую часть состояния объектов потока.

ws/ endl/ ends/ flush

Вывод состоит в том, что setw является единственным манипулятором моей версии, который не является липким.

Для Чарльза простой трюк влияет только на следующий предмет в цепочке:
Вот пример того, как объект может использоваться для временного изменения состояния, а затем вернуть его с помощью объекта:

#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

Ответ 2

Причина, по которой width не выглядит "липкой", заключается в том, что определенным операциям гарантируется вызов .width(0) в потоке вывода. Это:

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]: все do_put перегрузки для шаблона num_put. Они используются при перегрузках operator<< с использованием basic_ostream и встроенного числового типа.

22.2.6.2.2 [lib.locale.money.put.virtuals]: все do_put перегрузки для шаблона money_put.

27.6.2.5.4 [lib.ostream.inserters.character]: перегрузки operator<< с использованием basic_ostream и одного из типов char для создания базового_потока или char, подписанного char или unsigned char или указатели на массивы этих типов char.

Честно говоря, я не уверен в обосновании этого, но никакие другие состояния ostream не должны быть reset отформатированными выходными функциями. Конечно, такие вещи, как badbit и failbit, могут быть установлены, если в выходной операции есть сбой, но этого следует ожидать.

Единственная причина, по которой я могу думать о сбросе ширины, это то, что было бы удивительно, если бы при попытке вывести некоторые поля с разделителями ваши разделители были дополнены.

например.

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

"   4.5     |   3.6      \n"

Чтобы "исправить", это займет:

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

тогда как при ширине сброса желаемый выход может быть сгенерирован короче:

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

Ответ 3

setw() влияет только на следующую вставку. Это ведет себя так, как ведет себя setw(). Поведение setw() такое же, как ios_base::width(). Я получил информацию setw() от cplusplus.com.

Вы можете найти полный список манипуляторов здесь. Из этой ссылки все флаги потока должны указывать, пока они не будут изменены другим манипулятором. Одна заметка о манипуляторах left, right и internal: они похожи на другие флаги и сохраняются до тех пор, пока не будут изменены. Тем не менее, они имеют эффект только при установке ширины потока, а ширина должна быть установлена ​​в каждой строке. Итак, например

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

даст вам

>     a
>     b
>     c

но

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

даст вам

>     a
>b
>c

Манипуляторы ввода и вывода не являются липкими и встречаются только один раз, когда они используются. Параметрированные манипуляторы отличаются друг от друга, здесь краткое описание каждого из них:

setiosflags позволяет вручную устанавливать флаги, список которых может быть установлен здесь, поэтому он липкий.

resetiosflags ведет себя подобно setiosflags за исключением того, что он отключает указанные флаги.

setbase устанавливает базу целых чисел, вставленных в поток (поэтому 17 в базе 16 будет "11", а в базе 2 будет "10001" ).

setfill устанавливает символ заполнения для вставки в поток, когда используется setw.

setprecision устанавливает десятичную точность, которая будет использоваться при вставке значений с плавающей запятой.

setw делает только следующую вставку указанной ширины, заполняя символ, указанный в setfill