Подтвердить что ты не робот

Липкий пользовательский манипулятор потока

Как реализовать собственный собственный манипулятор потока, чтобы он был липким. Например, я хочу преобразовать целые числа в двоичные, чтобы:

cout << "decimal of 4: " <<  4 
     << "\ndecimal of 4: " << 4 
     << binary << "\nbinary of 4: " << 4 
     << "\nbinary of 4: " << 4 
     << nobinary << "\ndecimal of 4: " << 4 
     << "\ndecimal of 4: " << 4 << endl;

вернется:

decimal of 4: 4
decimal of 4: 4
binary of 4: 100
binary of 4: 100
decimal of 4: 4
decimal of 4: 4
4b9b3361

Ответ 1

Выполнение всего этого немного связано. Чтобы сделать его понятным, я начну с основных вещей: использование пользовательских флагов форматирования для пользовательских типов. Пользовательское форматирование целых чисел будет следовать ниже.

Классы IOStream получают [косвенно] из std::ios_base, который предоставляет два хранилища для данных: std::ios_base::iword() и std::ios_base::pword() для int и void* соответственно. Сохранение выделенной памяти, хранящейся в std::ios_base::pword(), является нетривиальным и, к счастью, не требуется для этого относительно простого варианта использования. Чтобы использовать эти функции, которые возвращают ссылку не на const на соответствующий тип, вы обычно выделяете индекс с помощью std::ios_base::xalloc() один раз в своей программе и используете его всякий раз, когда вам нужно получить доступ к своим пользовательским флагам форматирования. Когда вы получаете доступ к значению с помощью iword() или pword(), изначально он будет инициализирован нолем. Чтобы свести дело, вот небольшая программа, демонстрирующая это:

#include <iostream>

static int const index = std::ios_base::xalloc();

std::ostream& custom(std::ostream& stream) {
    stream.iword(index) = 1;
    return stream;
}

std::ostream& nocustom(std::ostream& stream) {
    stream.iword(index) = 0;
    return stream;
}

struct mytype {};
std::ostream& operator<< (std::ostream& out, mytype const&) {
    return out << "custom-flag=" << out.iword(index);
}

int main()
{
    std::cout << mytype() << '\n';
    std::cout << custom;
    std::cout << mytype()  << '\n';
    std::cout << nocustom;
    std::cout << mytype() << '\n';
}

Теперь int как 4 не является определяющим пользователем типом, и для них уже существует оператор вывода. К счастью, вы можете настроить способ форматирования целочисленных чисел с использованием граней, более конкретно используя std::num_put<char>. Теперь для этого вам нужно выполнить несколько шагов:

  • Вывести класс из std::num_put<char> и переопределить члены do_put(), которым вы хотите дать специализированное поведение.
  • Создайте объект std::locale, используя вновь созданный грань.
  • std::ios_base::imbue() поток с новым std::locale.

Чтобы сделать пользователя более приятным, вы можете создать новый std::locale с подходящей гранью std::num_put<char> при использовании манипулятора. Однако, прежде чем делать это, позвольте начать с создания подходящей грани:

#include <bitset>
#include <iostream>
#include <limits>
#include <locale>

static int const index = std::ios_base::xalloc();

class num_put
    : public std::num_put<char>
{
protected:
    iter_type do_put(iter_type to,
                     std::ios_base& fmt,
                     char_type fill,
                     long v) const
    {
        if (!fmt.iword(index)) {
            return std::num_put<char>::do_put(to, fmt, fill, v);
        }
        else {
            std::bitset<std::numeric_limits<long>::digits> bits(v);
            size_t i(bits.size());
            while (1u < i && !bits[i - 1]) {
                --i;
            }
            for (; 0u < i; --i, ++to) {
                *to = bits[i - 1]? '1': '0';
            }
            return to;
        }
    }
#if 0
    // These might need to be added, too:
    iter_type do_put(iter_type, std::ios_base&, char_type,
                     long long) const;
    iter_type do_put(iter_type, std::ios_base&, char_type,
                     unsigned long) const;
    iter_type do_put(iter_type, std::ios_base&, char_type,
                     unsigned long long) const;
#endif
};

std::ostream& custom(std::ostream& stream) {
    stream.iword(index) = 1;
    return stream;
}

std::ostream& nocustom(std::ostream& stream) {
    stream.iword(index) = 0;
    return stream;
}

int main()
{
    std::locale loc(std::locale(), new num_put);
    std::cout.imbue(loc);
    std::cout << 13 << '\n';
    std::cout << custom;
    std::cout << 13  << '\n';
    std::cout << nocustom;
    std::cout << 13 << '\n';
}

Что является немного уродливым, так это, чтобы imbue() пользовательский std::locale использовать манипулятор custom. Чтобы избавиться от этого, мы можем просто убедиться, что пользовательский фасет установлен в используемом std::locale, а если нет, просто установите его при установке флага:

std::ostream& custom(std::ostream& stream) {
    if (!stream.iword(index)
        && 0 == dynamic_cast<num_put const*>(
                &std::use_facet<std::num_put<char> >(stream.getloc()))) {
        stream.imbue(std::locale(stream.getloc(), new num_put));
    }
    stream.iword(index) = 1;
    return stream;
}

Теперь вы должны также переопределить различные члены do_put() для правильной работы с различными типами unsigned и с long long, но это остается как упражнение.