Использование std :: streams для форматирования вывода

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

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

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

этот код примерно иллюстрирует то, что я хочу.

Item item;
std::cout << "generic formatted:" << item;
std::cout << "custom formatted:" << CustomItemFormat() << item;

но это может быть невозможно или нецелесообразно.

как предполагается использовать потоковую библиотеку для решения таких проблем?


person daramarak    schedule 31.01.2010    source источник
comment
Добавлено требование хранить формат отдельно от класса Item.   -  person daramarak    schedule 31.01.2010


Ответы (7)


Попробуйте использовать шаблон дизайна Посетитель:

struct Object_Writer_Interface
{
  virtual void write_member_i(int value) = 0;
  virtual void write_member_c(char value) = 0;
};

struct Object
{
    int i;
    char c;
    void write(Object_Writer_Interface * p_writer)
    {
        if (p_writer)
        {
            p_writer->write_member_i(i);
            p_writer->write_member_c(c);
        }
    }
};

struct Stream_Object_Writer
  : public Object_Writer_Interface
{
    Stream_Object_Writer(std::ostream& out)
       : m_out(out)
       { ; }
    void write_member_i(int value)
    {
        m_out << i << '\n';
    }
    void write_member_c(char c)
    {
        m_out << "'" << c << "'\n";
    }
    std::ostream& m_out;
};


int main(void)
{
    Object  o;
    o.i = 5;
    o.c = 'M';

    // Create a writer for std::cout
    Stream_Object_Writer writer(std::cout);

    // Write the object to std::cout using the writer
    o.write(&writer);

    return EXIT_SUCCESS;
}

В этом дизайне для записи объектов в сокет вы наследуете класс от Object_Writer_Interface для обработки сокета. Точно так же, если вы хотите сериализовать Object.

В настоящее время я использую эту технику для записи объектов в списки графического интерфейса пользователя, записи базы данных и файлы Xml. В отличие от метода перегрузки operator <<, мне не пришлось изменять основной класс при разработке новых Writers.

person Thomas Matthews    schedule 31.01.2010
comment
Что ж, похоже, что другой разработчик Java пытается написать C ++ - person Martin York; 01.02.2010
comment
@ Мартин Йорк, не могли бы вы уточнить этот комментарий. С чем в этом решении вы не согласны? - person daramarak; 01.02.2010
comment
Все, что я сказал, это похоже на решение проблемы на Java (т.е. это не очень похоже на C ++). Две проблемы. 1) Потому что это не C ++, так как его будет сложно интегрировать с библиотеками, попробуйте и быть похожими на C ++ (STL, ускорение приходит в голову). 2) существует тесная связь между Object и Object_Writer_Interface. Любое изменение реализации Object потребует переписывания интерфейса и всех его зависимостей. - person Martin York; 01.02.2010
comment
@Martin York, почему это не С ++? Это стандартная реализация, и я реализовал ее за много лет до того, как изучил Java. В моих реализациях и во всех реализациях, над которыми я работал, писатели меняются чаще, чем объекты. При добавлении элементов к объекту должны измениться как operator <<, так и приведенный выше дизайн. Суть этого дизайна в том, что новые Writers не вызывают изменений в методе Object по сравнению с методом operator <<. Приведите пример реализации C ++, если это не так. - person Thomas Matthews; 01.02.2010
comment
Попробуйте использовать этот метод с любым из стандартных алгоритмов. Например, std :: copy, используя std :: istream_iterators или любые другие вещи, которые происходят в STL. - person Martin York; 01.02.2010

Лично я бы написал набор средств форматирования.
Средства форматирования должны знать внутреннее устройство объекта, который они форматируют, но подружить их не должно быть большой проблемой.

class X
{ friend std::ostream& operator<<(std::ostream& str,XML_Format const& formatter);
  friend std::ostream& operator<<(std::ostream& str,Json_Format const& formatter);
  friend std::ostream& operator<<(std::ostream& str,Fred_Format const& formatter);
  public: int value() const {return 5;}
};
struct XML__Foram   { X const& print; XML_Format(X const& v):   print(v) {} };
struct Json_Format  { X const& print; Json_Format(X const& v):  print(v) {} };
struct Fred_Format  { X const& print; Fred_Format(X const& v):  print(v) {} };

std::ostream& operator<<(std::ostream& str,XML_Format const& formatter)
{
     return str << "<XObj>" << formatter.print.value() << "</XObj>";
}
std::ostream& operator<<(std::ostream& str,Json_Format const& formatter)
{
     return str << "{XObj:{" << formatter.print.value() << "}}";
}
std::ostream& operator<<(std::ostream& str,Fred_Format const& formatter)
{
     return str << "Killl Kill Kill. Friday 13th";
}

int main()
{
     X   obj;
     std::cout << XML_Format(obj) << std::endl;
}
person Martin York    schedule 31.01.2010
comment
Да, это почти то, о чем я думал. Однако мне не нравится сильная связь между форматом и объектом. Хотелось бы изменить реализацию класса, которому нужен формат, без изменения форматов. - person daramarak; 31.01.2010
comment
Вместо пользовательских форматеров и friend я использую шаблон Visitor и создаю собственные Writers. Поэтому, не добавляя новые operator << для каждого нового формата, я просто получаю от интерфейса Writer. Таким образом, мне не нужно изменять рабочий класс. - person Thomas Matthews; 31.01.2010
comment
@ Томас: Iys 6 из полдюжины другого. Мои методы пишут новый оператор потока. Ваш метод напишет новый интерфейс Writter. Муфта такая же. - person Martin York; 01.02.2010

Да, возможно, волшебное слово, которое вы ищете, - это потоковые манипуляторы.

Ознакомьтесь с этим вопросом: Пользовательский манипулятор потока C ++, который изменится следующим элемент в потоке

person John Carter    schedule 31.01.2010
comment
Я знал о манипуляторах потоков, но не знал, как они могут решить эту проблему за меня. Мне придется взглянуть на этот подход. - person daramarak; 31.01.2010
comment
Похоже, это можно сделать так, но мне кажется, что моя проблема не должна решаться таким образом. Это больше для управления самим потоком, а не выводом объекта. Возможно, декоратор - правильный способ сделать это ... - person daramarak; 31.01.2010

Вам нужно написать манипулятор потока, который хранит информацию в потоке, который затем используется operator<<(std::ostream&,const Item&). См. Начало этого ответа о том, как хранить пользовательские данные в потоке.

person sbi    schedule 31.01.2010
comment
Проблема здесь в том, что элемент (или оператор потоковой передачи элемента) должен знать обо всех форматах. Я хочу разделить формат и элемент, потому что я знаю, что у элемента много разных форматов, но я не знаю, какой из них мне придется использовать. - person daramarak; 31.01.2010

Что такого плохого в Item.CustomItemFormat ()?

В качестве альтернативы есть несколько шаблонов проектирования, направленных на решение этой проблемы, а именно шаблон посетителя.

У вас также может быть CustomItemFormat (Item)?

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

person Eric    schedule 31.01.2010
comment
Предмет должен иметь возможность показывать себя, но не должен знать о его форматах. Это будет означать, что каждый раз, когда появляется новый формат, элементы должны быть изменены. Можно использовать шаблон декоратора, хотя его будет несколько непрактично использовать при обработке многих элементов или, возможно, других элементов, которые также используют данный формат. - person daramarak; 31.01.2010
comment
Как бы элемент мог проявить себя, если бы он не знал, в каком формате показывать себя? - person sbi; 31.01.2010
comment
Как отмечалось выше, эту проблему должен решить шаблон посетителя. Пока он знает, что показывать, ему не нужно ничего знать о формате. - person daramarak; 01.02.2010

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

Имеет ли это смысл?

person daramarak    schedule 31.01.2010

IOStreams не очень подходят для этого, но вы можете использовать библиотеку форматирования, такую ​​как {fmt} что обеспечивает лучшую расширяемость. Например:

struct Item {
  int value;
};

template <>
struct fmt::formatter<Item> {
  enum format { generic, custom };
  format f = generic;

  constexpr auto parse(parse_context &ctx) {
    auto it = ctx.begin(), end = ctx.end();
    if (it == end) return it;
    if (*it == 'g') f = generic;
    else if (*it == 'c') f = custom;
    else return it;
    return ++it;
  }

  template <typename FormatContext>
  auto format(const Item& item, FormatContext &ctx) {
    return format_to(ctx.out(), f == generic ? "{}" : "{:x}", item.value);
  }
};

Затем вы можете использовать Item объекты с любой функцией форматирования, например print:

fmt::print("{}", Item{42}); // Default generic format - value formatted as decimal
fmt::print("{:g}", Item{42}); // Explicit generic format
fmt::print("{:c}", Item{42}); // Custom format - value formatted as hex

Заявление об ограничении ответственности: я являюсь автором {fmt}.

person vitaut    schedule 16.12.2018