我需要将日志记录添加到遗留c ++项目中,该项目包含数百个用户定义的结构/类。这些结构只包含int
,float
,char[]
,enum
等主要类型。
只要可以重建对象,就需要记录对象的内容,以人类可读的方式进行记录,但不是必须的。
不是为每个类编写不同的序列化方法,还有其他方法吗?
答案 0 :(得分:0)
由于C ++没有反射,因此无法在运行时动态检查对象的成员。因此,您需要为每种类型编写特定的序列化/流/日志记录功能。
如果所有不同的类型都有相同名称的成员,那么你可以编写一个模板函数来处理它们,但我认为情况并非如此。
答案 1 :(得分:0)
由于C ++没有反射,这并不容易。 如果您想避免使用冗长的解决方案,可以使用可变参数模板。
E.g。 `class MyStruct { 私人的: int a; float f;
公共: void log() { log_fields(a,f); } };`
其中log_fields()是可变参数模板。它需要专门针对那些用户定义类型上的所有基本类型以及递归案例。
答案 2 :(得分:0)
你想要的是Program Transformation System (PTS)。这些工具可以读取源代码,构建代表源代码的编译器数据结构(通常是AST),并允许您修改AST并从修改后的AST中重新生成源代码。
这些是有用的,因为他们“走出去”"语言,因此对您可以分析或转换的内容没有语言限制。因此,如果你的语言没有反映一切,那就无所谓了;一个好的PTS可以让你完全访问语言的每个细节,包括注释和数字文字的基数。
某些PTS特定于目标语言(例如," Jackpot"仅适用于Java)。一个非常好的PTS提供了任意编程语言的描述,然后可以操纵该语言。该描述必须使PTS能够解析代码,分析它(至少构建符号表)并对解析/修改的结果进行漂亮打印。
好的PTS将允许您使用 source-to-source 转换编写您想要进行的修改。这些规则指定了大致以下形式编写的更改:
if you see *this*, replace it by *that* when *condition*
其中此和 是模式,使用正在处理的目标语言的语法,条件是必须为true的谓词(test)才能启用规则。模式表示格式良好的代码fragmens,通常允许元变量表示任意子片段的占位符。
您可以将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 主要用于扫描代码以查找要序列化的合适结构。当它找到一个struct的typedef时,它会检查struct是否是一个OP想要序列化的结构(否则就可以忽略 if 条件)。元函数选择是自定义编码的(此处未显示)以识别感兴趣的结构的名称。当找到合适的typedef语句时,它将被typedef(因此保留它)替换,并由包含议程项 log_members 的序列化例程的shell替换,该例程包含结构的整个成员列表。 (如果代码以其他方式声明结构,例如,作为类,则需要其他规则来识别这些情况的语法)。通过重复重写来处理议程项目会为各个成员生成日志操作。
规则用DMS规则语法编写; C ++模式写在 metaquotes &#34; ...&#34; 使DMS能够区分规则语法和C ++语法。占位符变量 v 根据语法类别在规则标题中声明,并使用转义符号 \ v 显示在元引用的模式中。 [注意选中的函数调用中未转义的 i :它不在metaquotes中。类似地,metaquotes中的元函数和模式引用也被类似地转义,因此最初奇怪的看起来是 \ log \(... \),包括转义的模式名称,以及转义的元括号。
两个规则 generate_member_log_xxx 处理日志生成的一般和最终情况。一般情况处理一个成员要做更多成员;最后一个案例处理最后一个成员。 (稍有不同的变体是通过重写为简单的空语句; 来处理空成员列表)。这基本上是一个列表,直到你结束。我们欺骗&#34;并编写相当简单的日志代码,依靠流写入的重载来处理OP声称他拥有的不同数据类型。如果他有更复杂的类型需要特殊处理(例如,指向... ),他可能想要编写识别这些情况并生成不同代码的专门规则。
规则集 generate_logging 将这些规则打包成一个整洁的捆绑包。您可以简单地要求DMS在整个文件上运行此规则集,应用规则直到无法进一步应用规则。 serialize_typdef_structure 规则查找感兴趣的结构,生成序列化函数shell和 log_members 议程项,这些项重复重写以生成成员的序列化。
这是基本的想法。我还没有测试过这段代码,并且通常会有一些令人惊讶的语法变化,你最终必须处理这些变化,这意味着在同一行上再写一些规则。
但是一旦实现,您可以在代码上运行此规则以获取序列化结果。 (可以实现 selected 来拒绝已经具有序列化例程的命名结构,或者添加用新生成的代码替换任何现有序列化代码的规则,确保序列化过程始终与struct定义匹配) 。生成序列化结构读取器有明显的扩展。
你可以用Clang和/或Rose编译器实现这些相同的想法。但是,这些系统没有为您提供源到源重写规则,因此您必须编写程序代码来爬行和爬下树,检查单个节点等。这是恕我直言,更多的工作和更少的可读性。 / p>
当你遇到下一个问题时,C ++并没有反映出这个问题,你可以使用同一个工具解决问题: - }