Восстановить состояние std :: cout после манипулирования им

106

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

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..)?

Спасибо.

УльтраИнстинкт
источник
Я считаю, что заклинание длится только до следующей смены. Изменение будет постоянным, только если вы измените флаги формата вручную, а не с помощью манипуляторов.
Билли Онил,
4
@BillyONeal: Нет, использование манипуляторов дает тот же эффект, что и изменение флагов формата вручную. :-P
Крис Джестер-Янг
3
Если вы здесь из-за того, что Коверти обнаружил, что не восстанавливает формат ostream (STREAM_FORMAT_STATE) , см . Обнаружение покрытия: не восстанавливается формат ostream (STREAM_FORMAT_STATE) .
jww
Я сделал нечто подобное - см. Мой вопрос о Code Review: используйте стандартный поток, а затем восстановите его настройки .
Тоби Спейт
1
Этот вопрос - прекрасный пример того, почему iostream не лучше stdio. Только что обнаружил две неприятные ошибки из-за не- / полу- / полностью / чего-не постоянного iomanip.
fuujuhi

Ответы:

99

вам нужно #include <iostream>или #include <ios>тогда, когда потребуется:

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

//Your code here...

cout.flags( f );

Вы можете поместить их в начало и конец своей функции или посмотреть этот ответ о том, как использовать это с RAII .

Стефан Кендалл
источник
5
@ ChrisJester-Young, на самом деле хороший C ++ - это RAII, особенно в таком случае!
Alexis Wilke
4
@Alexis Я на 100% согласен. См. Мой ответ (Boost IO Stream State Saver). :-)
Крис Джестер-Янг
3
Это не безопасно.
einpoklum
2
Помимо флагов, есть еще кое-что о состоянии потока.
jww
3
Вы можете избежать проблемы, не помещая форматы в потоки. Вставьте формат и данные во временную переменную строкового потока, затем распечатайте
Марк Шерред
63

Функция Boost IO Stream State Saver кажется именно тем, что вам нужно. :-)

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

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 Stream State Saver имеет несколько классов для сохранения разных частей состояния потока, из которых ios_flags_saverтолько один.
Крис Джестер-Янг,
3
Если вы думаете, что стоит заново реализовать и поддерживать каждую мелочь самостоятельно, вместо того, чтобы использовать проверенную, хорошо протестированную библиотеку ...
jupp0r
46

Обратите внимание, что ответы, представленные здесь, не восстановят полное состояние 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
rr-
источник
Хотя на мой первоначальный вопрос был дан ответ несколько лет назад, этот ответ является отличным дополнением. :-)
UltraInstinct
2
@UltraInstinct Кажется, это лучшее решение, и в этом случае вы можете и, вероятно, должны сделать его принятым ответом.
underscore_d
По некоторым причинам это вызывает исключение, если для потока включены исключения. coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
anton_rh
1
Кажется, что std::iosон всегда в плохом состоянии, потому что у него есть NULLrdbuf. Таким образом, установка состояния с включенными исключениями вызывает выброс исключения из-за плохого состояния. Решения: 1) Используйте какой-нибудь класс (например std::stringstream) с rdbufset вместо 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;
}
qbert220
источник
2
Отлично, если кто-то бросает, у вас все еще есть правильные флаги в вашем потоке.
Alexis Wilke
4
Помимо флагов, есть еще кое-что о состоянии потока.
jww
1
Я действительно хочу, чтобы С ++ позволил попробовать / наконец. Это отличный пример работы 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..
}
whacko__Cracko
источник
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&.

n.caillou
источник
Хороший момент [+], но он, конечно, помнит об использовании std::stringstreamдля части форматирования, как указал Марк Шерред .
Wolf
@Wolf Я не уверен, что понимаю вашу точку зрения. An std::stringstream - это std:ostream, за исключением того, что он вводит дополнительный промежуточный буфер.
n.caillou
Конечно, оба подхода к форматированию вывода являются допустимыми, оба представляют объект потока, тот, который вы описываете, является для меня новым. Теперь я должен подумать о плюсах и минусах. Тем не менее, вдохновляющий вопрос с поучительными ответами ... (я имею в виду вариант потокового копирования)
Вольф
1
Вы не можете скопировать поток, потому что копирование буферов часто не имеет смысла (например, stdout). Однако у вас может быть несколько потоковых объектов для одного и того же буфера, что и предлагает сделать этот ответ. Принимая во внимание, что std:stringstreamбудет создавать свой собственный независимый std:stringbuf( std::streambufпроизводный), который затем нужно std::cout.rdbuf()
влить
Спасибо за разъяснения.
Wolf
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 не позволяет мне сделать это из-за отсутствия репутации. Так заставьте меня загромождать здесь ответы вместо простого комментария ...

Дж. Уайлд
источник