【问题标题】:Using std::streams to format output使用 std::streams 格式化输出
【发布时间】:2011-01-11 10:17:32
【问题描述】:

我有一个我希望能够流式传输的对象。但是我希望能够通过使用不同的格式以不同的方式流式传输它,或者我应该说描述这个对象的方式。我想知道这应该如何用流来解决。

我想要的是能够使用通用格式并使用某种格式适配器将通用格式转换为首选格式。

我还希望能够将格式与 Item 的实现分开,这样我就不必每次添加或更改新格式时都更改 Item。

这段代码大致说明了我想要什么。

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

但这可能不可行或不切实际。

面对此类问题,流式库打算如何使用?

【问题讨论】:

  • 添加了将格式与 Item 类分开的要求。

标签: c++ format stream std


【解决方案1】:

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} 的作者。

【讨论】:

    【解决方案2】:

    尝试使用 Visitor 设计模式:

    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 派生一个类来处理套接字。同样,如果您想序列化对象。

    我目前正在使用这种技术将对象写入 GUI 列表框、数据库记录和 Xml 文件。与重载operator &lt;&lt; 的技术不同,我在开发新的Writers 时不必修改原理类。

    【讨论】:

    • 看来另一个 Java 开发人员正在尝试编写 C++
    • @Martin York,您能否详细说明该评论。你不同意这个解决方案中的什么?
    • 我所说的只是它看起来像是解决问题的 Java 解决方案(即它不是很像 C++)。两个问题。 1)因为它不像 C++,所以很难与库集成,所以尝试像 C++(STL,boost jump to mind)。 2) Object 和 Object_Writer_Interface 之间存在紧密耦合。对 Object 实现的任何更改都需要重写接口及其所有依赖项。
    • @Martin York,为什么这不像 C++?这是一个标准实现,我在学习 Java 之前已经实现了很多年。在我的实现以及我从事的所有实现中,Writers 比 Objects 更频繁地更改。向对象添加成员时,operator &lt;&lt; 和上述设计都必须更改。这种设计的重点是新的 Writers 不会导致 Object 与 operator &lt;&lt; 方法发生变化。如果不是,请给出 C++ 实现的示例。
    • 尝试将此方法与任何标准算法一起使用。例如 std::copy,使用 std::istream_iterators 或 STL 中发生的任何其他事情。
    【解决方案3】:

    我个人会写一套格式化程序。
    格式化程序必须知道他们正在格式化的对象的内部结构 但让他们成为朋友应该没什么大不了的。

    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;
    }
    

    【讨论】:

    • 是的,这几乎就是我的想法。我不喜欢格式和对象之间的强耦合。它想更改需要格式的类的实现,而不必更改格式。
    • 我没有使用自定义格式化程序并使用friend,而是使用Visitor 模式并创建自定义Writers。因此,无需为每种新格式添加新的operator &lt;&lt;,我只需从 Writer 接口派生即可。因此我没有修改工人阶级。
    • @Thomas:一个半打中的 6 个。我的方法编写了一个新的流运算符。您的方法编写了一个新的 Writer 接口。耦合是一样的。
    【解决方案4】:

    我想得越多,我开始怀疑我是否以错误的方式接近这个问题。也许我应该有一个通用的格式接口并设置项目的格式,以便项目可以使用这种格式输出自己。

    这有意义吗?

    【讨论】:

      【解决方案5】:

      您需要编写一个流操纵器,它将信息存储在流中,然后由operator&lt;&lt;(std::ostream&amp;,const Item&amp;) 使用。请参阅this 答案的开头了解如何将用户数据存储在流中。

      【讨论】:

      • 这里的问题是 Item(或 Item 的流式操作符)必须了解所有格式。我想将 Item 的格式分开,因为我知道 Item 有很多不同的格式,但我不知道我必须使用哪种格式。
      【解决方案6】:

      Item.CustomItemFormat() 有什么不好?

      另外还有一些旨在解决这个问题的设计模式,即访问者模式。

      你也可以有 CustomItemFormat(Item)?

      我不认为让流来解决这个问题是正确的方向,你应该把展示自己的任务委托给 Item 类

      【讨论】:

      • 该项目应该能够显示自己,但它不应该知道它的格式。这意味着每次出现新格式时,都必须更改项目。可以使用装饰器模式,尽管在处理许多项目时使用它有点不切实际,或者可能是其他类型的项目,它也使用给定的格式。
      • 如果一个项目不知道以哪种格式显示自己,它怎么能显示自己?
      • 如上所述,访问者模式应该可以解决这个问题。只要它知道要显示什么,它就不需要知道任何格式。
      【解决方案7】:

      是的,有可能,您正在寻找的神奇词是流manipulators

      看看这个问题:C++ custom stream manipulator that changes next item on stream

      【讨论】:

      • 我知道流操纵器,但不知道它们如何为我解决这个问题。我将不得不看看这种方法。
      • 看起来可以这样做,但在我看来,我的问题不应该像这样解决。更多的是操纵该流本身,而不是对象的输出。也许装饰器是这样做的正确方法......
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-19
      相关资源
      最近更新 更多