c++ - 如何在C++中记录用户定义的POD结构

标签 c++ logging serialization reflection

我需要将日志记录添加到旧的c ++项目中,该项目包含数百个用户定义的结构/类。这些结构仅包含主要类型,如intfloatchar[]enum

对象的内容需要记录,最好以人类可读的方式记录,但不是必须的,只要可以重建对象即可。

除了为每个类编写不同的序列化方法外,还有其他替代方法吗?

最佳答案

您想要的是Program Transformation System (PTS)。这些工具可以读取源代码,构建代表源代码的编译器数据结构(通常是AST),并允许您修改AST并从修改后的AST重新生成源代码。

这些功能很有用,因为它们“跳出了语言”,因此对您可以分析或转换的内容没有语言限制。因此,如果您的语言没有对所有内容进行反思,那也没关系;一个好的PTS可以让您完全访问该语言的每个细节,包括注释和数字文字基数等奥秘。

一些PTS特定于目标语言(例如,“ Jackpot”仅适用于Java)。一个非常好的PTS提供了对任意编程语言的描述,然后可以操纵该语言。该描述必须使PTS能够解析代码,对其进行分析(至少构建符号表)并漂亮地打印解析/修改后的结果。

好的PTS允许您编写要使用源到源转换进行的修改。这些规则指定以以下大致形式编写的更改:

   if you see *this*, replace it by *that* when *condition*


其中this和that是使用正在处理的目标语言的语法的模式,而condition是谓词(测试),必须为true才能启用规则。这些模式代表格式良好的代码片段,通常允许元变量代表任意子片段的占位符。

您可以将PTS用于各种程序处理任务。对于OP来说,他想要的是枚举程序中的所有结构,挑选出感兴趣的子集,然后为每个选定的结构生成一个序列化器,作为对原始程序的修改。

为了实际执行此特定任务,PTS必须能够解析和命名解析(构建符号表)C ++。很少有工具可以做到这一点:Clang,我们的DMS软件再造工具包和Rose编译器。

使用DMS的解决方案如下所示:

domain Cpp~GCC5;  -- specify the language and specific dialect to process

pattern log_members( m: member_declarations ): statements = TAG;
      -- declares a marker we can place on a subtree of struct member declarations

rule serialize_typedef_struct(s: statement, m: member_declarations, i: identifier):
           statements->statements
   = "typedef struct { \m } \i;" -> 
     "typedef struct { \m } \i;
      void \make_derived_name\(serialize,\i) ( *\i argument, s: stream )
          { s << "logging" << \toString\(\i\);
            \log_members\(\m\)
          }"
      if selected(i); -- make sure we want to serialize this one

rule generate_member_log_list(m: member_declarations, t: type_specification, n: identifier): statements -> statements
   " \log_members\(\t \n; \m\)" -> " s << \n; \log_members\(\m\) ";

rule generate_member_log_base(t: type_specification, n: identifier): statements -> statements
   " \log_members\(\t \n; \)" -> " s << \n; ";

ruleset generate_logging {
   serialize_typedef struct,
   generate_member_log_list,
   generate_member_log_base 
}


域声明告诉DMS使用哪种特定的语言前端。是的,GCC5作为方言与VisualStudio2013不同,DMS可以处理其中任何一种。

模式log_members用作一种转换指针,以记住有一些工作要做。它将struct member_declarations的序列包装为议程(标签)。规则要做的是首先用log_members标记感兴趣的结构,以建立生成日志记录代码的需求,然后生成成员日志记录操作。 log_members模式充当列表;一次处理一个元素,直到处理完最后一个元素,然后达到目的的log_members标记消失。

规则serialize_typedef_struct本质上用于扫描代码以寻找合适的要序列化的结构。当它找到一个结构的typedef时,它会检查该结构是否是OP想要序列化的结构(否则,可以忽略if条件)。所选的元功能经过自定义编码(此处未显示)以识别感兴趣的结构的名称。找到合适的typedef语句后,将其替换为typedef(因此保留它),并替换为包含议程项目log_members的序列化例程的外壳,该议程项目保留了该结构的成员的整个列表。 (如果代码以其他方式声明结构,例如,作为一个类,则将需要其他规则来识别这些情况的语法)。通过重复重写来处理议程项目,将为各个成员生成日志操作。

规则以DMS规则语法编写; C ++模式写在元引号“ ...”内,以使DMS区分规则语法和C ++语法。占位符变量v根据其语法类别在规则标头中声明,并使用转义符号\ v显示在元引用模式中。 [请注意所选函数调用中未转义的i:它不在metaquotes中]。类似地,元引号内的元函数和模式引用也类似地转义,因此最初看起来很奇怪,\ log \(... \)包括转义的模式名称和转义的元括号。

这两个规则generate_member_log_xxx处理了日志生成的一般情况和最终情况。一般情况下,一个成员要处理的事更多。最后一个案例处理最后一个成员。 (一个细微的变化是通过重写琐碎的null语句来处理空的成员列表;)。从本质上讲,这是一个列表,直到您掉到最后为止。我们“欺骗”并编写相当简单的日志记录代码,依靠流写入的重载来处理OP声称具有的不同数据类型。如果他具有需要特殊处理的更复杂的类型(例如指向...的指针),则他可能想编写识别这些情况并产生不同代码的专门规则。

规则集generate_logging将这些规则打包为一个整齐的捆绑包。您可以简单地要求DMS在整个文件上运行此规则集,应用规则直到无法进一步应用规则。 serialize_typdef_structure规则找到感兴趣的结构,生成序列化功能外壳程序和log_members议程项目,将它们重复重写以产生成员的序列化。

这是基本思想。我没有测试过这段代码,通常必须处理一些令人惊讶的语法变化,这意味着沿着同一行编写更多规则。

但是一旦实现,您就可以在代码上运行此规则以获得序列化的结果。 (可以选择一种实现来拒绝已具有序列化例程的命名结构,或者添加规则以新生成的代码替换任何现有的序列化代码,以确保序列化过程始终与结构定义匹配)。生成序列化的结构阅读器有明显的扩展。

您可以说可以使用Clang和/或Rose Compiler实现这些相同的想法。但是,这些系统没有为您提供源到源的重写规则,因此您必须编写过程代码才能爬上和爬下树,检查单个节点等。恕我直言,这需要更多的工作,而且可读性也要差很多。

当您遇到下一个“ C ++不能反映这一点”时,可以使用相同的工具解决问题:-}

关于c++ - 如何在C++中记录用户定义的POD结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38218905/

相关文章:

c++ - 将 auto_ptr 传递给需要常量引用 auto_ptr 的函数有什么危险?

c# - C++ 是否支持单个泛型方法而不是泛型类?

c# - 当 .Net Thread 没有名称时,如何使用 NLog 打印 threadid 而不是 threadname?

scala - 如何在databricks中添加scala代码日志记录?

logging - 如何在jboss 6 AS中轮换日志文件

serialization - 你能把一个正在运行的 Lua 协程转储到一个文件中以便以后恢复吗?

安卓房间 : @Ignore vs Transient

java - 一旦方法返回,传递给方法并反序列化的列表将变为空

c++ - 根据消息调用函数

c++ - 基数排序功能出现问题