Які маніпулятори iomanip є "липкими"?


140

Нещодавно у мене виникли проблеми із створенням 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), схоже, це не чітко показує.

Відповіді:


87

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

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

@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або покажчики на масиви цих типів гольців.

Якщо чесно, я не впевнений в обґрунтованості цього, але жоден інший стан A не 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';

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

Тільки тестуючи шістнадцятковий і setiosflags (), вони обидва здаються липкими (вони обидва просто встановлюють прапори, які зберігаються для цього потоку, поки ви їх не зміните).
Девід Браун

Так, веб-сторінка, яка стверджувала, std::hexщо не є липкою, була помилковою - я теж це з’ясувала. Однак прапори потоку можуть змінитися, навіть якщо ви не вставите std::setiosflagsзнову, так що це можна побачити як липке. Також std::wsне липке. Так що це НЕ що легко.
sbi

Ви доклали чимало зусиль для вдосконалення своєї відповіді. +1
sbi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.