我需要将日志记录添加到遗留的c++项目中,该项目包含数百个用户定义的结构/类。这些结构只包含主类型int
、float
、char[]
、enum
。
对象的内容需要被记录,最好是以人类可读的方式,但不是必须的,只要对象可以被重建。
除了为每个类编写不同的序列化方法之外,还有其他方法吗?
您想要的是一个程序转换系统(PTS)。这些工具可以读取源代码,构建表示源代码的编译器数据结构(通常是AST),并允许您修改AST和从修改后的AST重新生成源代码。
这些是有用的,因为它们"走出"了语言,因此对您可以分析或转换的内容没有语言强加的限制。因此,如果你的语言没有对所有事情进行反思,那也没关系;一个好的PTS将使您能够完全访问语言的每一个细节,包括注释和数字文本基数等晦涩难懂的内容。
一些PTS是特定于目标语言的(例如,"Jackpot"仅适用于Java)。一个真正好的PTS提供了一个任意编程语言的描述,然后可以操作该语言。该描述必须使PTS能够解析代码、分析代码(至少构建符号表)并预打印解析/修改的结果。
良好的PTSE将允许您使用源到源转换来编写您想要进行的修改。这些规则规定了大致以以下形式编写的更改:
if you see *this*, replace it by *that* when *condition*
其中this和that是使用正在处理的目标语言语法的模式,并且condition为谓词(测试),必须为true才能应用规则。这些模式表示格式良好的代码片段,通常允许元变量表示任意子片段的占位符。
你可以使用创伤后应激障碍来完成各种各样的程序操作任务。对于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用作一种转换指针,以记住还有一些工作要做。它将结构member_declaration的序列包装为议程(标记)。规则所做的是首先用log_members标记感兴趣的结构,以确定生成日志记录代码的需要,然后生成成员日志记录操作。log_members模式充当列表;它一次处理一个元素,直到处理完最后一个元素为止,然后logmembers标记消失,达到了它的目的。
规则serialize_typedef_struct主要用于扫描代码,寻找合适的结构进行序列化。当它找到一个结构的typedef时,它会检查结构是否是OP想要序列化的结构(否则,如果是有条件的,就可以省略)。所选的元函数是自定义编码的(此处未显示),用于识别感兴趣的结构的名称。当找到合适的typedef语句时,它会被typedef替换(从而保留它),并被包含议程项log_members的序列化例程的外壳替换,该议程项包含结构的整个成员列表。(如果代码以其他方式声明structs,例如作为类,则需要额外的规则来识别这些情况的语法)。通过反复重写议程项目来处理议程项目,会为各个成员产生日志操作。
这些规则是用DMS规则语法编写的;C++模式写在metaquotes"…">中,使DMS能够区分规则语法和C++语法。占位符变量v根据其语法类别在规则头中声明,并使用转义符\v显示在元引号模式中。[注意所选函数调用中未标注的i:它不在元引号内]。类似地,元引号中的元函数和模式引用也进行了类似的转义,因此最初看起来很奇怪,包括转义的模式名称和转义的元括号。
generate_member_log_xxx这两个规则提供了日志生成的一般情况和最终情况。一般情况下处理一个成员与多个成员之间的关系;最后一个案例处理最后一个成员。(一个轻微的变体是通过重写到琐碎的null语句来处理空成员列表;)。这基本上就是沿着一个列表往下走,直到你掉到最后。我们"作弊"并编写相当简单的日志记录代码,指望流写入的过载来处理OP声称拥有的不同数据类型。如果他有更复杂的类型需要特殊处理(例如,指向…的指针),他可能希望编写专门的规则来识别这些情况并生成不同的代码。
规则集generate_logging将这些规则打包成一个整洁的捆绑包。您可以简单地要求DMS在整个文件上运行此规则集,应用规则直到无法进一步应用任何规则为止。serialize_typdef_structure规则查找感兴趣的结构,生成序列化函数shell和log_members议程项,它们被重复重写以生成成员的序列化。
这是基本的想法。我还没有测试过这段代码,通常会有一些令人惊讶的语法变化,你最终不得不处理,这意味着要在同一行中再写一些规则。
但是一旦实现,就可以在代码上运行此规则以获得序列化的结果。(可以实现selected来拒绝已经有序列化例程的命名结构,或者添加规则,用新生成的代码替换任何现有的序列化代码,确保序列化过程始终与结构定义匹配)。生成序列化结构读取器有一个明显的扩展。
您可以使用Clang和/或Rose编译器来实现这些相同的思想。然而,这些系统并没有为您提供源代码到源代码的重写规则,因此您必须编写过程代码来爬上爬下树、检查单个节点等。IMHO需要做更多的工作,可读性也要差得多。
当你遇到下一个"C++没有反映这一点"时,你可以用同样的工具来解决这个问题:-}
由于C++没有反射,因此无法在运行时动态检查对象的成员。因此,您需要为每种类型编写一个特定的序列化/流式传输/日志记录函数。
如果所有不同的类型都有相同名称的成员,那么您可以编写一个模板函数来处理它们,但我认为情况并非如此。
由于C++没有反射,所以这并不容易。如果您想避免冗长的解决方案,可以使用可变模板。
例如。`类MyStruct{私人:int a;浮子f;
公共:void log(){log_fields(a,f);}};`
其中log_fields()是可变模板。它需要专门用于那些用户定义类型上的所有基本类型,也需要用于递归情况。