自动生成结构体/类的流操作符

12

有没有一种工具可以自动生成结构体或类的 << 操作符?

输入(摘自“统治它们所有:一个调试打印函数”的答案):

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有一个。对于您来说,运算符<<的输出格式是否重要? - johnathan
JSON,XML,任何人类可读的格式都可以。 - Christopher Oezbek
你可能可以使用Boost PP和MPL来生成它。 - David
字段必须命名吗? - Matthieu M.
这将是首选。 - Christopher Oezbek
5个回答

3
需要的是一种能够准确解析C ++,枚举各种类/结构,确定并根据每个类/结构生成您的“序列化”的工具,然后将生成的代码放置在“正确的位置”(可能是找到该结构的相同范围)。它需要完整的预处理器来处理实际代码中指令的扩展。
我们的DMS软件重构工具包及其C ++ 11前端可以做到这一点。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 序列的形式描述,则可以自动应用序列上的大量算法。这里,关联序列 最好。
因为我们正在谈论元编程,所以映射将一个类型与一个类型化值相关联。
您可以使用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更喜欢一个外部解决方案,可以将结构粘贴到其中并生成运算符代码,然后再将生成的代码复制回来。我想要输出的大多数结构都在外部库中。 - Christopher Oezbek
@ChristopherOezbek:我不知道是否有任何“集成”工具可以做到这一点(我认为太专业了),但如果您不想支付DMS(Ira的答案),那么我建议您对Clang项目感兴趣。使用libclang,您可以解析C++文件,然后操作AST。您可以在clang dev邮件列表上提出具体问题。 - Matthieu M.
@MatthieM:谢谢!Boost::Fusion对我的另一个项目很有兴趣! - Christopher Oezbek

1
为了实现这一点,唯一的方法是使用一个外部工具,在源文件上运行它。
首先,您可以使用c/c++分析工具,并使用它从源代码中检索解析树。然后,一旦您获得了解析树,您只需要搜索结构即可。 对于每个结构,您现在可以生成一个operator<<重载,以序列化结构的字段。您还可以生成反序列化运算符。
但这取决于您有多少结构:对于十几个结构,最好手动编写运算符,但如果您有数百个结构,则可能要编写(反)序列化运算符生成器。

0

您可以使用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/


0

我对你的问题有两种理解。

如果你想要生成程序的自动状态报告,我建议你检查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中的正则表达式。 这种解决方案的主要缺点是您与结构“脱机”,即每次更改内容都必须运行它。

Benoit。


主要问题是我不想自己编写序列化方法,因为这是枯燥乏味的工作。由于我想输出的所有结构体都很简单,所以我希望能够自动生成serialize()或operator<<()代码。 - Christopher Oezbek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接