Должен ли я создать временный поток с помощью другого streambuf?

Предположим, что у меня есть функция, которая принимает параметр ostream & o и записывает этот поток. Реализация operator << будет хорошим примером.

ostream& operator << (ostream& o, const MyThing& t)
{
  // ... interesting code here ...
  return o;
}

Внутри функции я могу указать параметры форматирования в потоке. Например, мне может потребоваться, чтобы число было напечатано как hex, независимо от того, как o настроен, когда он передается функции.

Во-вторых, я могу захотеть сделать предположения о текущих флагах форматирования. Например, было бы неплохо иметь возможность предположить, что числа были отформатированы как десятичные, если я не попрошу иначе.

Наконец, к моменту выхода функции я хочу, чтобы параметры форматирования на o были такими же, как они были до того, как была вызвана функция, чтобы они не изменились для вызывающего. Это просто вопрос вежливости вызывающему.

До сих пор я достиг этого, создав локальную ostringstream внутри функции, выполнив всю свою работу над этим (включая параметры форматирования параметров) и отправив .str() в o в конце функции, Вопрос StackOverflow здесь говорит о том, что люди умнее меня используют один и тот же подход.. Однако мне мешает, что я храню столько данных в ostringstreams, которые, возможно, могут быть отправлены на выход раньше (строки могут стать довольно большими). ​​

У меня есть два вопроса:

1) Является ли это законной, идиоматической, хорошей формой и т.д. создавать временный (основанный на стеке) поток вокруг o.rdbuf() и выполнять мою работу над этим ostream? Мои собственные тесты и страница cppreference.com показывают, что я могу.

ostream& operator << (ostream& o_, const MyThing& t)
{
  ostream o (o_.rdbuf());
  // write stuff to "o",
  // setting formatting options as I go.
  return o_; // Formatting on the parameter ostream o_ unchanged.
}

2) Есть ли другой, лучший способ, который я не рассматривал?

Ответ 2

Это не плохое решение; это, безусловно, законно. Я не думаю он слишком распространен, поэтому, вероятно, неплохо прокомментировать почему вы это делаете.

Наиболее частым решением, которое я видел здесь, является создание состояния класс сохранения, который сохранит все необходимое вам состояние (обычно flags(), precision() и fill()) в конструктор и восстановить его в деструкторе, а затем принудительно установите все нужные параметры. (Возможно, это возможно использовать copyfmt для этого, хотя это также копирует такие вещи, как маска исключений, с которой вы, вероятно, не хотите играть.)

Ответ 3

Настройки могут быть сохранены в виде объекта, называемого объектом fmtflags, определенного в классе с именем ios, который включен в iostream. Вы можете объявить один из эти объекты, но вы должны объявить его с помощью оператора разрешения области.

Следующий оператор сохранит определенные аспекты состояния формата в переменной old_settings:

ios::fmtflags old_settings = cout.flags();

Затем, после выполнения вывода с использованием нового параметра, вы можете восстановить старый параметр, вызвав ту же функцию со старыми настройками в качестве аргумента:

cout.flags(old_settings);

Другие настройки могут быть получены и восстановлены с помощью функций-членов. Например,

int old_precision = cout.precision();

сохранит текущую спецификацию точности. Тогда

cout.precision(old_precision);

вернет точность к исходному значению