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

144

Недавно у меня возникла проблема с созданием stringstreamиз-за того, что я ошибочно предположил, 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), похоже, не ясно показывает это.
Джон К
источник
Рабочий

Ответы:

91

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

Автор: Мартин:

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

Чарльз:

В яблочко! и единственная причина, по которой 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
Мартин Йорк
источник
Хорошая шпаргалка. Добавьте ссылку на источник информации, и это будет идеальный ответ.
Марк Рэнсом,
1
Однако я могу проверить, что setfill () на самом деле «липкий», хотя и возвращает объект. Я думаю, что это неверный ответ.
Джон К.
2
Объекты, возвращающие поток, должны быть закрепленными, в то время как те, которые возвращают объект, могут быть липкими, но это не обязательно. Я обновлю ответ с помощью информации Джона.
Мартин Йорк,
1
Я не уверен, что понимаю ваши рассуждения. Все манипуляторы, которые принимают параметры, реализованы как бесплатные функции, возвращающие неуказанный объект, который действует в потоке, когда этот объект вставляется в поток, поскольку это единственный (?) Способ сохранить синтаксис вставки с параметрами. В любом случае, соответствующий operator<<манипулятору гарантирует, что состояние потока будет изменено определенным образом. Ни одна из форм не устанавливает никакого государственного караула. Только поведение следующей операции форматированной вставки определяет, какая часть состояния сбрасывается, если таковая имеется.
CB Bailey,
3
В яблочко! и единственная причина, которая, setwкажется, ведет себя иначе, состоит в том, что существуют требования к операциям форматированного вывода для явного .width(0)вывода потока вывода.
CB Bailey,
31

Причина, 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и один из символьного типа в basic_ostream конкретизации или char, подписанный charили unsigned charили указатели на массивы этих типов гольцов.

Честно говоря, я не уверен в обосновании этого, но никакие другие состояния не ostreamдолжны сбрасываться функциями форматированного вывода. Конечно, такие вещи, как 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';
CB Bailey
источник
6

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

Дэвид Браун
источник
Что ж, большинство из них просто устанавливают флаги, так что они «липкие». setw () кажется единственным, который влияет только на одну вставку. Вы можете найти более подробную информацию по каждому из них на cplusplus.com/reference/iostream/manipulators
Дэвид Браун
Ну std::hexи не липкие и, очевидно, std::flushили std::setiosflagsне липкий либо. Так что я не думаю, что это так просто.
SBI, 07
Просто протестируйте hex и setiosflags (), они оба кажутся липкими (они оба просто устанавливают флаги, которые сохраняются для этого потока, пока вы их не измените).
Дэвид Браун,
Да, веб-страница, которая утверждала, std::hexчто не является липкой, была неправильной - я тоже только что узнал об этом. Флаги потока, однако, могут измениться, даже если вы не вставите std::setiosflagsснова, поэтому это можно будет увидеть как неприлипающее. Кроме того, std::wsне липнет. Так что это не что легко.
SBI 07
Вы приложили немало усилий, чтобы улучшить свой ответ. +1
sbi