Какие манипуляторы iomanip являются «липкими»? - PullRequest
130 голосов
/ 07 октября 2009

У меня недавно была проблема с созданием 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), по-видимому, не совсем ясно показывает это.

Ответы [ 3 ]

82 голосов
/ 07 октября 2009

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

Мартин:

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

Чарльз:

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

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


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

setiosflags
resetiosflags
setbase
setfill
setprecision
setw

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

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

Все остальные манипуляторы возвращают объект потока. Таким образом, любая информация о состоянии, которую они изменяют, должна быть записана в объекте потока и, таким образом, является постоянной (пока другой манипулятор не изменит состояние). Таким образом, следующие манипуляторы должны быть 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
30 голосов
/ 07 октября 2009

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

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

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

1032 * Е.Г. *

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';
6 голосов
/ 07 октября 2009

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

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

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 позволяет вам вручную устанавливать флаги, список которых может быть fount здесь , поэтому он липкий.

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

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

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

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

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

...