Відновити стан std :: cout після маніпулювання ним


105

Припустимо, у мене такий код:

void printHex(std::ostream& x){
    x<<std::hex<<123;
}
..
int main(){
    std::cout<<100; // prints 100 base 10
    printHex(std::cout); //prints 123 in hex
    std::cout<<73; //problem! prints 73 in hex..
}

Моє запитання, чи є спосіб відновити стан coutйого початкового після повернення з функції? (Дещо схоже std::boolalphaі std::noboolalpha..)?

Дякую.


Я вважаю, що шістнадцятковий триває лише до наступної операції з вимикання. Зміна зберігається лише в тому випадку, якщо ви змінюєте прапори формату вручну замість використання маніпуляторів.
Біллі ONeal

4
@BillyONeal: Ні, використання маніпуляторів має такий же ефект, як і зміна прапорів формату вручну. :-P
Кріс Єстер-Янг

3
Якщо ви знаходитесь тут через знахідку Covertiy Не відновлення формату потоку (STREAM_FORMAT_STATE) , то перегляньте статтю Результат покриття: Не відновлення формату потоку (STREAM_FORMAT_STATE) .
jww

Я зробив щось подібне - дивіться моє запитання щодо Code Review: Використовуйте стандартний потік та відновіть його налаштування після цього .
Toby Speight

1
Це питання є прекрасним прикладом того, чому iostream не кращий за stdio. Щойно знайшли двох неприємних помилок через не- / напів- / повністю- / що-не стійкого іоманіпу.
fuujuhi

Відповіді:


97

вам потрібно #include <iostream>або #include <ios>тоді, коли потрібно:

std::ios_base::fmtflags f( cout.flags() );

//Your code here...

cout.flags( f );

Ви можете поставити їх на початку та в кінці своєї функції або ознайомитись з цією відповіддю про те, як це використовувати з RAII .


5
@ ChrisJester-Young, насправді гарний C ++ - це RAII, особливо у такому випадку!
Алексіс Вільке

4
@ Алексис Я на 100% згоден. Дивіться мою відповідь (Boost IO Stream State Saver). :-)
Кріс Єстер-Янг

3
Це не безпечно для винятків.
einpoklum

2
Окрім прапорів, до стану потоку є більше.
jww

3
Ви можете уникнути проблеми, не натискаючи формати на потоки. Перемістіть формат і дані у тимчасову змінну струнного потоку, а потім надрукуйте
Марк Шерред

63

Збільшення стану потоку введення-виведення IOS здається саме тим, що вам потрібно. :-)

Приклад на основі фрагмента коду:

void printHex(std::ostream& x) {
    boost::io::ios_flags_saver ifs(x);
    x << std::hex << 123;
}

1
Зауважте, що тут немає ніякої магії, яка в ios_flags_saverосновному просто зберігає та встановлює прапори, як у відповіді @ StefanKendall.
einpoklum

15
@einpoklum Але це безпечно для винятків, на відміну від іншої відповіді. ;-)
Кріс Єстер-Янг

2
Окрім прапорів, до стану потоку є більше.
jww

4
@jww Бібліотека потоку державних потоків IO має кілька класів для збереження різних частин стану потоку, з яких ios_flags_saverлише один.
Кріс Єстер-Янг

3
Якщо ви думаєте, що варто повторно доповнювати та підтримувати кожну дрібницю самостійно, а не використовувати перевірену, добре перевірену бібліотеку ...
jupp0r

45

Зауважте, що наведені тут відповіді не відновлять повний стан std::cout. Наприклад, std::setfill"приклеїться" навіть після дзвінка .flags(). Кращим рішенням є використання .copyfmt:

std::ios oldState(nullptr);
oldState.copyfmt(std::cout);

std::cout
    << std::hex
    << std::setw(8)
    << std::setfill('0')
    << 0xDECEA5ED
    << std::endl;

std::cout.copyfmt(oldState);

std::cout
    << std::setw(15)
    << std::left
    << "case closed"
    << std::endl;

Буде надруковано:

case closed

а не:

case closed0000

Хоча на моє оригінальне запитання було відповідено кілька років тому, ця відповідь - чудове доповнення. :-)
UltraInstinct

2
@UltraInstinct Виявляється, що це краще рішення, і в цьому випадку ви можете, ймовірно, повинні зробити це прийнятим натомість.
підкреслити_29

Це з певних причин кидає виняток, якщо для потоку включені винятки. coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
anton_rh

1
Здається, std::iosце завжди в поганому стані, оскільки він має NULLrdbuf. Тож встановлення стану з включеними винятками спричиняє викид виключень через поганий стан. Рішення: 1) Використовуйте деякий клас (наприклад std::stringstream) з rdbufнабором замість std::ios. 2) Збережіть стан винятків окремо до локальної змінної та відключіть їх раніше state.copyfmt, а потім відновіть виняток із змінної (і зробіть це ще раз після відновлення стану, з oldStateякого вимкнення виключено). 3) Встановити , rdbufщоб std::iosце подобається:struct : std::streambuf {} sbuf; std::ios oldState(&sbuf);
anton_rh

22

Я створив клас RAII, використовуючи приклад коду з цієї відповіді. Велика перевага цієї методики виникає, якщо у вас є кілька шляхів повернення від функції, яка встановлює прапори на iostream. Який би шлях повернення не використовувався, деструктор завжди буде викликатися, а прапори завжди скидаються. Немає шансів забути відновити прапори, коли функція повернеться.

class IosFlagSaver {
public:
    explicit IosFlagSaver(std::ostream& _ios):
        ios(_ios),
        f(_ios.flags()) {
    }
    ~IosFlagSaver() {
        ios.flags(f);
    }

    IosFlagSaver(const IosFlagSaver &rhs) = delete;
    IosFlagSaver& operator= (const IosFlagSaver& rhs) = delete;

private:
    std::ostream& ios;
    std::ios::fmtflags f;
};

Потім ви використовуєте його, створюючи локальний екземпляр IosFlagSaver, коли хочете зберегти поточний стан прапора. Коли цей екземпляр вийде за межі, стан прапора буде відновлено.

void f(int i) {
    IosFlagSaver iosfs(std::cout);

    std::cout << i << " " << std::hex << i << " ";
    if (i < 100) {
        std::cout << std::endl;
        return;
    }
    std::cout << std::oct << i << std::endl;
}

2
Чудово, якщо хтось кине, ви все-таки отримали правильні прапори у своєму потоці.
Алексіс Вілке

4
Окрім прапорів, до стану потоку є більше.
jww

1
Я дуже хочу, щоб C ++ дозволено спробувати / нарешті Це прекрасний приклад, коли RAII працює, але, нарешті, було б простіше.
Trade-Ideas Philip

2
Якщо ваш проект хоч трохи розумний, у вас є Boost, і для цього потрібно поставити державні заощадження .
Ян

9

З невеликою кількістю модифікацій, щоб зробити результат більш читабельним:

void printHex(std::ostream& x) {
   ios::fmtflags f(x.flags());
   x << std::hex << 123 << "\n";
   x.flags(f);
}

int main() {
    std::cout << 100 << "\n"; // prints 100 base 10
    printHex(std::cout);      // prints 123 in hex
    std::cout << 73 << "\n";  // problem! prints 73 in hex..
}

9

Ви можете створити іншу обгортку навколо буфера stdout:

#include <iostream>
#include <iomanip>
int main() {
    int x = 76;
    std::ostream hexcout (std::cout.rdbuf());
    hexcout << std::hex;
    std::cout << x << "\n"; // still "76"
    hexcout << x << "\n";   // "4c"
}

У функції:

void print(std::ostream& os) {
    std::ostream copy (os.rdbuf());
    copy << std::hex;
    copy << 123;
}

Звичайно, якщо питання щодо продуктивності, це трохи дорожче, оскільки це копіювання всього iosоб’єкта (але не буфера), включаючи деякі речі, за які ви платите, але навряд чи використовуєте такі, як локаль.

Інакше мені здається, що якщо ти збираєшся використовувати, .flags()то краще бути послідовним і використовувати .setf(), а не <<синтаксис (чисто питання про стиль).

void print(std::ostream& os) {
    std::ios::fmtflags os_flags (os.flags());
    os.setf(std::ios::hex);
    os << 123;
    os.flags(os_flags);
}

Як вже сказала , що ви можете поставити вище (і , .precision()і .fill(), як правило , але не локал і слова , пов'язані з річчю , які, як правило , не мають наміру бути модифіковані та важче) в класі для зручності і щоб зробити це виняток безпечним; конструктор повинен прийняти std::ios&.


Хороший момент [+], але він, звичайно, пам'ятає використання std::stringstreamдля частини форматування, як вказував Марк Шерред .
Вовк

@Wolf Я не впевнений, що я розумію. std::stringstream Єstd:ostream , за винятком використання Вводиться додатковий проміжний буфер.
n.caillou

Звичайно, обидва є дійсними підходами до форматування виводу, обидва вводять об'єкт потоку, той, який ви описуєте, для мене новий. Зараз я думаю про плюси і мінуси. Однак надихаюче запитання із просвітливими відповідями ... (я маю на увазі варіант копіювання потоку)
Вовк

1
Ви не можете скопіювати потік, оскільки копіювання буферів часто не має сенсу (наприклад, stdout). Однак ви можете мати кілька об'єктів потоку для одного буфера, що саме ця пропозиція пропонує зробити. Тоді як std:stringstreamstd:stringbufstd::streambufstd::cout.rdbuf()
засіб

Дякуємо за роз’яснення.
Вовк

0

Я б хотів дещо узагальнити відповідь від qbert220:

#include <ios>

class IoStreamFlagsRestorer
{
public:
    IoStreamFlagsRestorer(std::ios_base & ioStream)
        : ioStream_(ioStream)
        , flags_(ioStream_.flags())
    {
    }

    ~IoStreamFlagsRestorer()
    {
        ioStream_.flags(flags_);
    }

private:
    std::ios_base & ioStream_;
    std::ios_base::fmtflags const flags_;
};

Це також повинно працювати для вхідних потоків та інших.

PS: Я хотів би зробити це просто коментарем до вищезгаданої відповіді, але stackoverflow не дозволяє мені це зробити через відсутність репутації. Таким чином, змушуйте мене чіпляти відповіді тут, а не простий коментар ...

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