【问题标题】:Auto-generate stream operator for struct/class为结构/类自动生成流运算符
【发布时间】:2012-05-16 22:24:09
【问题描述】:

是否有工具可以为结构或类自动生成 ostream

输入(取自One Debug-Print function to rule them all):

typedef struct ReqCntrlT    /* Request control record */
{
  int             connectionID;
  int             dbApplID;
  char            appDescr[MAX_APPDSCR];
  int             reqID;
  int         resubmitFlag;
  unsigned int    resubmitNo;
  char            VCIver[MAX_VCIVER];
  int             loginID;
}   ReqCntrlT;

输出:

std::ostream& operator <<(std::ostream& os, const ReqCntrlT& r) 
{
   os << "reqControl { "
      << "\n\tconnectionID: " << r.connectionID 
      << "\n\tdbApplID: " << r.dbApplID 
      << "\n\tappDescr: " << r.appDescr
      << "\n\treqID: " << r.reqID
      << "\n\tresubmitFlag: " << r.resubmitFlag
      << "\n\tresubmitNo: " << r.resubmitNo
      << "\n\tVCIver: " << r.VCIver
      << "\n\tloginID: " << r.loginID
      << "\n}";
   return os; 
}

任何工具都可以,首选 Python / Ruby 脚本。

【问题讨论】:

  • 可以尝试自动类序列化工具,我相信 boost 有一个。运算符
  • JSON、XML、任何人类可以阅读的东西我都可以。
  • 可以用 boost PP & MPL 生成它
  • 字段有必要命名吗?
  • 这是首选。

标签: c++ automation code-generation generator iostream


【解决方案1】:

为此需要一个工具,它可以准确地解析 C++、枚举各种类/结构、确定并生成每个类/结构的“序列化”,然后将生成的代码停放在“正确的place”(可能与找到该结构的范围相同)。它需要一个完整的预处理器来处理实际代码中指令的扩展。

我们的DMS Software Reengineering Toolkit 及其C++11 front end 可以做到这一点。 DMS 通过提供通用解析/AST 构建、符号表构建、流和自定义分析、转换和源代码重新生成功能,支持构建自定义工具。 C++ 前端使 DMS 能够解析 C++ 并构建准确的符号表,以及将修改后的或新的 AST 漂亮地打印回可编译的源格式。 DMS 及其 C++ 前端已用于对 C++ 代码进行大量转换。

你必须向 DMS 解释你想做什么;枚举符号表条目似乎很简单,询问结构/类类型声明,确定声明的范围(记录在符号表条目中),通过组合表面语法模式构造 AST,然后应用转换来插入构造的 AST。

所需的核心表面语法模式是插槽和函数体的语法模式:

 pattern ostream_on_slot(i:IDENTIFIER):expression =
   " << "\n\t" << \tostring\(\i\) << r.\i "; -- tostring is a function that generates "<name>"

 pattern ostream_on_struct(i:IDENTIFIER,ostream_on_slots:expression): declaration =
   " std::ostream& operator <<(std::ostream& os, const \i& r) 
     { os << \tostring\(\i\) << " { " << \ostream_on_slots << "\n}";
       return os; 
     }

必须为 ostream_on_slot 组合单独的树:

 pattern compound_ostream(e1:expression, e2:expression): expression
     = " \e1 << \e2 ";

使用这些模式,可以直接枚举结构的槽,为主体构造 ostream,并将其插入到结构的整体函数中。

【讨论】:

    【解决方案2】:

    有两种主要方法可以做到这一点:

    • 使用外部解析工具(例如与 Clang 绑定挂钩的 Python 脚本)
    • 使用元编程技术

    ..当然它们可以混合使用。

    我没有足够的关于 Clang Python 绑定的知识来回答使用它们,所以我将专注于元绘图。


    基本上,您要求的内容需要自省。 C++ 不支持完全自省,但是使用元编程技巧(和模板匹配)它可以在编译时支持有限的自省技术子集,这足以满足我们的目的。

    为了轻松混合元编程和运行时操作,使用库更容易:Boost.Fusion

    如果您调整您的结构,使其属性根据 Boost.Fusion 序列来描述,那么您可以自动在序列上应用大量算法。在这里,associate sequence 是最好的。

    因为我们正在讨论元编程,the maptypetyped 值 相关联。

    然后您可以使用 for_each 迭代该序列。


    我将忽略细节,只是因为已经有一段时间了,我不记得所涉及的语法,但基本上我的想法是:

    // Can be created using Boost.Preprocessor, but makes array types a tad difficult
    DECL_ATTRIBUTES((connectionId, int)
                    (dbApplId, int)
                    (appDescr, AppDescrType)
                    ...
                    );
    

    这是声明 Fusion Map 及其相关标签的语法糖:

    struct connectionIdTag {};
    struct dbApplIdTag {};
    
    typedef boost::fusion::map<
        std::pair<connectionIdTag, int>,
        std::pair<dbApplIdTag, int>,
        ...
        > AttributesType;
    AttributesType _attributes;
    

    然后,任何需要应用于属性的操作都可以简单地构建:

    // 1. A predicate:
    struct Predicate {
        template <typename T, typename U>
        void operator()(std::pair<T, U> const&) const { ... }
    };
    
    // 2. The for_each function
    for_each(_attributes, Predicate());
    

    【讨论】:

    • @MatthieM:Di 更喜欢外部解决方案,我将结构粘贴到其中并按生成,然后复制回生成的操作员代码。我想输出的大部分结构都在外部库中。
    • @ChristopherOezbek:我不知道有任何“集成”工具可以解决这个问题(我认为太专业了),但如果你不想为 DMS 付费(Ira 的回答),那么我建议你对 Clang 项目感兴趣。使用 libclang,您可以解析 C++ 文件,然后对 AST 进行操作。您可以在 clang dev 邮件列表中提出具体问题。
    • @MatthieM:谢谢! Boost::Fusion 看起来对我的另一个项目很感兴趣!
    【解决方案3】:

    要实现这一点,唯一的方法是使用在源文件上运行的外部工具。

    首先,您可以使用c/c++ analysing tool,并使用它从您的源代码中检索解析树。然后,一旦你有了解析树,你只需要搜索结构。 对于每个结构,您现在可以生成一个序列化结构字段的operator&lt;&lt; 重载。也可以生成反序列化操作符。

    但这取决于你有多少结构:最好是手动编写十几个结构,但如果你有数百个结构,你可能需要编写(反)序列化运算符生成器。

    【讨论】:

      【解决方案4】:

      我确实从两个方面理解了你的问题。

      如果您想生成程序的自动状态报告,我建议您检查 Boost.Serialization。 但是,它不会在编译时生成代码作为第一步或灵感。 下面的代码将帮助您生成可以阅读的 xml 或 txt 文件。

      typedef struct ReqCntrlT    /* Request control record */
      {
        int             connectionID;
        int             dbApplID;
        char            appDescr[MAX_APPDSCR];
        int             reqID;
        int         resubmitFlag;
        unsigned int    resubmitNo;
        char            VCIver[MAX_VCIVER];
        int             loginID;
      
          template<class Archive>
          void serialize(Archive & ar, const unsigned int version)
          {
              ar & connectionID;
              ar & reqID;
              ...
          }
      }   ReqCntrlT;
      

      查看教程了解更多详情:http://www.boost.org/doc/libs/1_49_0/libs/serialization/doc/index.html

      如果您只是想通过给出参数名称来“编写”代码。 然后你应该看看 python 或 perl 中的正则表达式。 此解决方案的主要默认设置是您的结构“离线”,即每次更改某些内容时都必须运行它。

      伯努瓦。

      【讨论】:

      • 主要问题是我不想自己编写序列化方法,因为它是无聊和乏味的工作。由于我要输出的所有结构都很简单,因此我想自动生成 serialize() 或 operator
      【解决方案5】:

      可以使用LibClang解析源码,生成ostream算子:

      # © 2020 Erik Rigtorp <erik@rigtorp.se>
      # SPDX-License-Identifier: CC0-1.0
      import sys
      from clang.cindex import *
      
      idx = Index.create()
      tu = idx.parse(sys.argv[1], ['-std=c++11'])
      
      for n in tu.cursor.walk_preorder():
          if n.kind == CursorKind.ENUM_DECL:
              print(
                  f'std::ostream &operator<<(std::ostream &os, {n.spelling} v) {{\n  switch(v) {{')
              for i in n.get_children():
                  print('    case {type}::{value}: os << "{value}"; break;'.format(
                      type=n.type.spelling, value=i.spelling))
              print('  }\n  return os;\n}')
          elif n.kind == CursorKind.STRUCT_DECL:
              print(
                  f'std::ostream &operator<<(std::ostream &os, const {n.spelling} &v) {{')
              for i, m in enumerate(n.get_children()):
                  print(
                      f'  os << "{", " if i != 0 else ""}{m.spelling}=" << v.{m.spelling};')
              print('  return os;\n}')
      

      来自我的文章:https://rigtorp.se/generating-ostream-operator/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-06-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-04
        • 2023-01-07
        • 2011-05-04
        相关资源
        最近更新 更多