Использовать стандартный поток, и восстановления его настройки после


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

Теперь мы можем "одолжить" входной или выходной поток, и вернуть его в состояние, в котором мы его нашли.

#include <ios>
#include <iomanip>

// Members are all public and mutable, so if we really don't want
// to restore any particular part of the state, we can override.
template<class CharT, class Traits = typename std::char_traits<CharT>>
struct save_stream_state
{
    std::basic_ios<CharT,Traits>& stream;
    std::ios_base::fmtflags flags;
    std::locale locale;
    std::streamsize precision;
    std::streamsize width;
    CharT fill;

    save_stream_state(std::basic_ios<CharT,Traits>& stream)
        : stream{stream},
          flags{stream.flags()},
          locale{stream.getloc()},
          precision{stream.precision()},
          width{stream.width()},
          fill{stream.fill()}
    {}

    // deleting copy construction also prevents move
    save_stream_state(const save_stream_state&) = delete;
    void operator=(const save_stream_state&) = delete;

    ~save_stream_state()
    {
        stream.flags(flags);
        stream.imbue(locale);
        stream.precision(precision);
        stream.width(width);
        stream.fill(fill);
    }
};

Это не строго необходимо удалить operator=как компилятор не будет генерировать для класса с не-статические ссылки на член, но я думаю, что лучше быть явным там.


Вот простая тестовая программа, которая показывает, как он используется. Если вы используете старую версию стандартов, шаблон тип вычета не будет работать - вам необходимо указать тип охраны в полном объеме (save_stream_state<char> и save_stream_state<wchar_t>).

#include <iostream>
int main()
{
    auto test = []() {
        std::cout << std::setw(15) << "Foo" << ' '
        << true << ' ' << 123456 << '\n';
    };
    {
        test();
        const save_stream_state guard{std::cout};
        std::cout << std::setfill('_') << std::left << std::uppercase
                  << std::boolalpha << std::hex << std::showbase;
        test();
    } // stream restored here
    test();


    std::cout << std::endl;


    // Now with wide-character stream:
    auto wtest = []() {
        std::wclog << std::setw(15) << L"Foo" << L' '
        << true << L' ' << 123456 << L'\n';
    };
    {
        wtest();
        // AAA style initialization - and guard multiple streams
        auto const guard = { save_stream_state{std::wclog},
                             save_stream_state{std::wcin} };
        std::wclog << std::setfill(L'_') << std::left << std::uppercase
                   << std::boolalpha << std::hex << std::showbase;
        wtest();
    } // stream restored here
    wtest();
}


415
5
задан 16 марта 2018 в 12:03 Источник Поделиться
Комментарии
1 ответ

Я согласен со всем, что @JiveDadson сказал в комментарии. Я считаю, что есть несколько способов, чтобы улучшить текущий код.

Синтаксис ##

Чтобы использовать предохранитель, необходимо ввести масштаб. Мой желаемый синтаксис будет такой:

stream << session << settings << data;

Я представлю решение с синтаксисом выше позже.

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

Именования

Было бы лучше использовать stream_state_guard? Код использует имя guard в ходе испытаний. Забавно, что тесты отражают то, что было на самом деле должно записываться. Я обнаружил, что это дело в моем коде тоже.


Альтернативное решение

Для достижения указанных выше синтаксис, я использовал некоторые новые особенности C++17. Мои мысли беспорядок больше, чем обычно, так что следите за потенциал, что доставит некоторое неудобство.

Во-первых, session должен быть объект какой-то пустой тип, как nullptr это nullptr_t или nullopt это nullopt_t.

struct session_t {};
static inline constexpr session_t session;

inline используется для борьбы с линковщиком.

Итак, сам класс.

#include <ios>
#include <iomanip>
#include <ostream>

class session_stream
{
std::ostream& stream;
std::ios_base::fmtflags flags;
std::locale locale;
std::streamsize precision;
std::streamsize width;
char fill;

session_stream(std::ostream& stream)
: stream{stream},
flags{stream.flags()},
locale{stream.getloc()},
precision{stream.precision()},
width{stream.width()},
fill{stream.fill()}
{}

public:
friend session_stream operator<<(std::ostream& os, session_t);

// deleting copy construction also prevents move
session_stream(const session_stream&) = delete;
void operator=(const session_stream&) = delete;

template <typename T>
std::ostream& operator<<(T&& value)
{
stream << std::forward<T>(value);
return stream;
}

~session_stream()
{
stream.flags(flags);
stream.imbue(locale);
stream.precision(precision);
stream.width(width);
stream.fill(fill);
}
};

Код просто скорректированный вариант от вопроса. Интересные вещи, чтобы отметить:


  • Конструктор и семантику копирования частная

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


  • Создать объект только с помощью stream << session

    Это может выглядеть подозрительно. Особенно эта строка:

    return session_stream{os};

    Начиная с C++17 это гарантированный пропуск копию, таким образом, это работает, даже если конструктор копирования недоступен. Я не думаю, что гарантирует невозможность продления срока службы session_streamно он будет охранять от случайного неправильного использования.


  • Использует свой собственный operator<< только на первый звонок

    Я не был уверен, что если перегрузках и АДЛ не вмешиваться, поэтому я решил вернуться ostream& на operator<<.


Переписать тесты:

#include <iostream>

int main()
{
auto test = []() {
std::cout << std::setw(15) << "Foo" << ' '
<< true << ' ' << 123456 << '\n';
};
{
test();
std::cout << session << std::setfill('_') << std::left << std::uppercase
<< std::boolalpha << std::hex << std::showbase << std::setw(15)
<< "Foo" << ' ' << true << ' ' << 123456 << '\n';
test();
} // stream restored here
test();
std::cout << std::endl;

}

Жить на Wandbox.

И просто для справки: занятия могут быть вложенными.

2
ответ дан 18 марта 2018 в 04:03 Источник Поделиться