简体   繁体   English

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

[英]How to log user defined POD struct in C++

I need to add logging to a legacy c++ project, which contains hundreds of user defined structs/classes. 我需要将日志记录添加到遗留c ++项目,该项目包含数百个用户定义的结构/类。 These structs only contain primary types as int , float , char[] , enum . 这些结构只包含主要类型,如intfloatchar[]enum

Content of objects need to be logged ,preferred in human readable way , but not a must, as long as the object could be reconstructed. 只要可以重建对象,就需要记录对象的内容,以人类可读的方式进行记录,但不是必须的。

Instead of writing different serialization methods for each class, is there any alternative method? 不是为每个类编写不同的序列化方法,还有其他方法吗?

Since C++ does not have reflection there is no way for you to dynamically inspect the members of an object at runtime. 由于C ++没有反射,因此无法在运行时动态检查对象的成员。 Thus it follows that you need to write a specific serialization/streaming/logging function for each type. 因此,您需要为每种类型编写特定的序列化/流/日志记录功能。

If all the different types had members of the same name, then you could write a template function to handle them, but I assume that is not the case. 如果所有不同类型的成员具有相同的名称,那么您可以编写一个模板函数来处理它们,但我认为情况并非如此。

As C++ does not have reflection this is not that easy. 由于C ++没有反射,因此并不容易。 If you want to avoid a verbose solution you can use a variadic template. 如果要避免使用详细解决方案,可以使用可变参数模板。

Eg `class MyStruct { private: int a; 例如`class MyStruct {private:int a; float f; 漂浮f;

public: void log() { log_fields(a, f); public:void log(){log_fields(a,f); } };` }

where log_fields() is the variadic template. 其中log_fields()是可变参数模板。 It would need to be specialized for all the basic types found on those user defined types and also for a recursive case. 它需要专门针对那些用户定义类型上的所有基本类型以及递归案例。

What you want is a Program Transformation System (PTS) . 你想要的是一个程序转换系统(PTS) These are tools that can read source code, build compiler data structures (usually ASTs) that represent the source code, and allow you to modify the ASTs and regenerate source code from the modified AST. 这些工具可以读取源代码,构建代表源代码的编译器数据结构(通常是AST),并允许您修改AST并从修改后的AST重新生成源代码。

These are useful because they "step outside" the language, and thus have no language-imposed limitations on what you can analyze or transform. 这些是有用的,因为它们“走出”语言,因此对你可以分析或转换的内容没有语言限制。 So it doesn't matter if your langauge doesn't have reflection for everything; 所以,如果你的语言没有反映一切,那就无所谓了; a good PTS will give you full access to every detail of the language, including such arcana as comments and radix on numeric literals. 一个好的PTS将让您完全访问该语言的每个细节,包括注释和数字文字的基数。

Some PTSes are specific to a targeted language (eg, "Jackpot" is only usuable for Java). 一些PTS特定于目标语言(例如,“Jackpot”仅可用于Java)。 A really good PTS is provided a description of an arbitrary programming langauge, and can then manipulate that language. 一个非常好的PTS提供了任意编程语言的描述,然后可以操纵该语言。 That description has to enable the PTS to parse the code, analyze it (build symbol tables at least) and prettyprint the parsed/modified result. 该描述必须使PTS能够解析代码,分析它(至少构建符号表)并对解析/修改的结果进行漂亮打印。

Good PTSes will allow you write the modifications you want to make using source-to-source transformations. 好的PTS将允许您使用源到源转换编写您想要进行的修改。 These are rules specifying changes written in roughly the following form: 这些规则指定了大致以下形式编写的更改:

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

where this and that are patterns using the syntax of the target language being processed, and condition is a predicate (test) that must be true to enable the rule to be applied. 其中这个 那个是使用目标语言的语法被处理模式 ,以及条件是谓词(测试)必须为真的以使得能够应用该规则。 The patterns represent well-formed code fragmens, and typically allow metavariables to represent placeholders for arbitrary subfragments. 模式表示格式良好的代码fragmens,并且通常允许元变量表示任意子片段的占位符。

You can use PTSes for a huge variety of program manipulation tasks. 您可以将PTS用于各种程序操作任务。 For OP's case, what he wants is to enumerate all the structs in the program, pick out the subset of interest, and then generate a serializer for each selected struct as a modification to the original program. 对于OP的情况,他想要的是枚举程序中的所有结构,挑选出感兴趣的子集,然后为每个选定的结构生成一个序列化程序,作为对原始程序的修改。

To be practical for this particular task, the PTS must be able to parse and name resolve (build symbol tables) C++. 为了实现这一特定任务,PTS必须能够解析并命名解析(构建符号表)C ++。 There are very few tools that can do this: Clang, our DMS Software Reengineering Toolkit, and the Rose compiler. 很少有工具可以做到这一点:Clang,我们的DMS软件再造工具包和Rose编译器。

A solution using DMS looks something like this: 使用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 
}

The domain declaration tells DMS which specific language front-end to use. 声明告诉DMS使用哪种特定语言前端。 Yes, GCC5 as a dialect is different than VisualStudio2013, and DMS can handle either. 是的,GCC5作为方言与VisualStudio2013不同,DMS也可以处理。

The pattern log_members is used as a kind of transformational pointer, to remember that there is some work to do. 模式 log_members用作一种转换指针,记住有一些工作要做。 It wraps a sequence of struct member_declarations as an agenda ( tag ). 它将一系列struct member_declarations包装为议程( 标记 )。 What the rules do is first mark structs of interest with log_members to establish the need to generate the logging code, and then generate the member logging actions. 规则的作用是首先使用log_members标记感兴趣的结构,以确定生成日志记录代码的需要,然后生成成员日志记录操作。 The log_members pattern acts as a list; log_members模式充当列表; it is processed one element at a time until a final element is processed, and then the log_members tag vanishes, having served its purpose. 它一次处理一个元素,直到处理完最终元素,然后log_members标记消失,达到了它的目的。

The rule serialize_typedef_struct is essentially used to scan the code looking for suitable structs to serialize. 规则 serialize_typedef_struct主要用于扫描代码以查找要序列化的合适结构。 When it finds a typedef for a struct, it checks that struct is one that OP wants serialized (otherwise one can just leave off the if conditional). 当它找到一个struct的typedef时,它会检查该struct是否是一个OP想要序列化的结构(否则就可以省略if条件)。 The meta-function selected is custom-coded (not shown here) to recognize the names of structs of interest. 选择的元功能自定义编码的(这里没有显示)承认的利益结构的名称。 When a suitable typedef statement is found, it is replaced by the typedef (thus preserving it), and by the shell of a serializing routine containing the agenda item log_members holding the entire list of members of the struct. 当找到合适的typedef语句时,它将被typedef(因此保留它)替换,并由包含议程项log_members的序列化例程的shell替换,该log_members包含结构的整个成员列表。 (If the code declares structs in some other way, eg, as a class , you will need additional rules to recognize the syntax of those cases). (如果代码以某种其他方式声明结构,例如,作为 ,则需要额外的规则来识别这些情况的语法)。 Processing the agenda item by rewriting it repeatedly produces the log actions for the individual members. 通过重复重写来处理议程项目会为各个成员生成日志操作。

The rules are written in DMS rule-syntax; 规则用DMS规则语法编写; the C++ patterns are written inside metaquotes " ... " to enable DMS to distinguish rule syntax from C++ syntax. C ++模式写在metaquotes “...”中,以使DMS能够区分规则语法和C ++语法。 Placeholder variables v are declared in the rule header according thier syntactic categories, and show up in the meta-quoted patterns using an escape notation \\v . 占位符变量v根据句法类别在规则标题中声明,并使用转义符号\\ v显示在元引用的模式中。 [Note the unescaped i in the selected function call: it isn't inside metaquotes]. [注意所选函数调用中未转义的i :它不在metaquotes中]。 Similarly, meta-functions and patterns references inside the metaquotes are similarly escaped, thus initially odd looking \\log\\( ... \\) including the escaped pattern name, and escaped meta-parentheses. 类似地,元引用内部的元函数和模式引用也被类似地转义,因此最初奇怪的是\\ log \\(... \\)包括转义的模式名称和转义的元括号。

The two rules generate_member_log_xxx hand the general and final cases of log generation. 两个规则generate_member_log_xxx处理日志生成的一般和最终情况。 The general case handles one member with more members to do; 一般情况处理一个成员要做更多成员; the final case handles the last member. 最后一个案例处理最后一个成员。 (A slight variant would be to process an empty members list by rewriting to the trivial null statement ; ). (稍有不同的变体是通过重写到普通的空语句来处理空成员列表; )。 This is essentially walking down a list until you fall off the end. 这基本上是一个列表,直到你结束。 We "cheat" and write rather simple logging code, counting on overloading of stream writes to handle the different datatypes that OP claims he has. 我们“欺骗”并编写相当简单的日志代码,依靠流写入的重载来处理OP声称他拥有的不同数据类型。 If he has more complex types requiring special treatment (eg, pointer to... ) he may want to write specialized rules that recognize those cases and produce different code. 如果他有更复杂的类型需要特殊处理(例如, 指向... ),他可能想要编写识别这些情况并生成不同代码的专门规则。

The ruleset generate_logging packages these rules up into a neat bundle. 规则集 generate_logging将这些规则打包成一个整齐的包。 You can trivially ask DMS to run this ruleset over entire files, applying rules until no rules can be further applied. 您可以简单地要求DMS在整个文件上运行此规则集,应用规则直到无法进一步应用规则。 The serialize_typdef_structure rule finds the structs of interest, generating the serializing function shell and the log_members agenda item, which are repeatedly re-written to produce the serialization of the members. serialize_typdef_structure规则查找感兴趣的结构,生成序列化函数shell和log_members议程项,这些项重复重写以生成成员的序列化。

This is the basic idea. 这是基本的想法。 I haven't tested this code, and there is usually some surprising syntax variations you end up having to handle which means writing a few more rules along the same line. 我还没有测试过这段代码,并且通常会有一些令人惊讶的语法变化,你最终必须处理它们,这意味着在同一行上再写一些规则。

But once implemented, you can run this rule over the code to get serialized results. 但是一旦实现,您可以在代码上运行此规则以获取序列化结果。 (One might implement selected to reject named structs that already have a serialization routine, or alternatively, add rules that replace any existing serialization code with newly generated code, ensuring that the serialization procedures always match the struct definition). (可以实现选择拒绝已经具有序列化例程的命名结构,或者添加用新生成的代码替换任何现有序列化代码的规则,确保序列化过程始终与结构定义匹配)。 There's the obvious extension to generating a serialized struct reader. 生成序列化结构阅读器有明显的扩展。

You can arguably implement these same ideas with Clang and/or the Rose Compiler. 你可以用Clang和/或Rose编译器来实现这些相同的想法 However, those systems do not offer you source-to-source rewrite rules, so you have to write procedural code to climb up and down trees, inspect individual nodes, etc. It is IMHO a lot more work and a lot less readable. 但是,这些系统不提供源到源重写规则,因此您必须编写程序代码来爬上树和树,检查单个节点等。这是恕我直言,更多的工作和更少的可读性。

And when you run into your next "C++ doesn't reflect that", you can tackle the problem with the same tool :-} 当你遇到下一个“C ++没有反映出来”时,你可以使用同一个工具解决问题: - }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM