C ++代码生成的优雅方式

6

我目前正在从事一个与数据库相关的项目,其中我生成了很多C++代码。这些代码被编译并作为动态库加载。我使用这种技术来构建数据库模式和查询的高效代码。

目前,我使用简单的文件写入来生成代码(这对概念验证实现来说还可以)。现在,我正在寻找一种更优雅但同样灵活的解决方案来生成C++代码。

我已经搜索了很多,但是到目前为止,我发现的所有解决方案都相当复杂/繁琐,不够高效或者不够灵活。

您在C++项目中使用哪些库来生成代码?

最好的祝福, Moritz


就我个人而言,我只使用Python来进行所有的代码生成,甚至没有使用任何专门用于代码生成的库(如Cheetah)。从未遇到过任何问题,而且由于它只是Python,每个人都能理解它。 - Barry
但我正在编写一个C++程序,它生成C++代码,作为共享库动态加载和执行。 - moo
1
我见过的最令人印象深刻的规则对象实现之一是由同事完成的。这个项目是用 Java 编写的,他编写了一个翻译器,可以从使用的规则语言(如果我没记错,它们是 Sexps)生成可编译的优化 Java 代码。将其发送到编译器(随产品一起提供),生成的 .class 文件被存储在数据库中,并通过雕刻出来的类加载器按需加载,然后发送到规则引擎以进行必要的评估。这太美妙了。我一直想尝试用 dll/so 文件和 C++ 做同样的事情。听起来与你正在做的很相似。 - WhozCraig
尝试使用 yb-orm https://github.com/vnaydionov/yb-orm/wiki - Sergei Nikulov
我不太确定为什么ORM能帮助我?它并不是非常灵活和适应我的需求。此外,我所工作的数据库项目并不是关系型数据库。 - moo
2
你可以尝试使用clang库为你编译代码。这似乎更加清晰,因为它避免了调用system()来运行编译器的操作。 - Tyler
1个回答

1

你可以使用程序转换系统(PTS)来可靠地定义和组合代码模板。

大多数PTS能够定义语法,然后使用该语法将源代码解析为AST。更重要的是,它们接受模式:源代码片段(通常是非终端符或非终端符列表),其中包含占位符,这些占位符对应于良好格式化的子片段(表示子树的非终端符)。这些模式通常坚持指定命名的占位符完全匹配(请参见下面的示例)。可以使用这些模式与解析的AST匹配,以查找使用表面语法的代码片段。

因此,可以使用一个模式:

   pattern x_squared(t: term): product
      = " \t * \t ";

寻找由相同子树的乘积组成的子表达式。这将匹配。
   (p + q[17])*(p+q[17)

但不是。
    2 * (x-3)

但同样有趣的是,这些模式可以用作代码生成器,通过使用变量的绑定值(树)来实例化该模式。 因此,“实例化x_squared(2 ^ x)”会产生
     (2^x)*(2^x)

单独看,这只是一种花哨的宏方案。它更好的地方在于可以告诉你“在编译时”(对于模式)你正在组合的内容是否有意义。因此,您可以获得代码片段组成的类型检查。例如,您可能会不小心编写“实例化x_squared(int q)”,但一个好的PTS会反对“int q”不是一个“术语”;您在构建代码生成器时发现了错误。
当您可以从许多不同的模式中构建许多不同的代码片段,并使用更多的模式组合这些片段时,这就变得非常有趣。这使您能够构建非常复杂的代码。所有这些都是(语法类型)安全的方式;生成的树是有效的语法。(您仍然可以搞砸语义;没有什么是完美的)。随着您可以生成的代码的复杂性的提高,拥有这种额外的检查有助于您避免生成错误的代码。
PTS还有另一个优点:在组合代码片段之后,它可以应用源到源转换以优化生成的代码。因此,您可以根据自己撰写匹配转换的能力和利用代码生成期间所掌握的知识来生成优化的代码。想象一下,您为矩阵乘法生成代码:
 ... P * Q ...

您的代码生成器以某种方式知道Q是一个单位矩阵。接下来的优化可以消除昂贵的矩阵乘法:
  rule optimize_matrix_times_unit(m: term, n: term): product -> product
       " \m * \q "
   ->  " \m "
    if is_identity_matrix(q)

这种转换利用了模式匹配(以查找矩阵乘积)、模式实例化(以生成匹配产品的替代品)和代码生成可以执行的其他知识或分析(is_identity_matrix)。
您需要一个能够处理C++解析的PTS;这有点难找。我设计的那个(DMS软件重构工具包)恰好可以做到这一点。本答案中的示例是DMS风格的。
这里有一篇技术论文,描述了DMS在C++代码上完成的大规模重构任务。该论文中的许多示例实际上是相当复杂的模式,用于实例化代码;重构任务必须为现有代码块生成一组新的API。

非常有趣的话题。我还不确定这种代码合成是否足够灵活适用于我的情况。我一定会仔细研究它。但我想这将是我在当前项目中提到的“未来工作”之一 ;) - moo
我可以保证,通过设计以及使用这个工具20多年的经验(请查看我的简介),它非常灵活;支持多种语言、多种代码生成/转换/分析任务。我们曾经用它生成过大型程序。你能描述一下你认为需要哪些灵活性吗?我可能能够提出建设性的回应。 - Ira Baxter
我认为我不需要你的工具的全部功能。目前,我有一个类似树形的C++代数运算符结构,您可以在根节点上调用produce来启动特定查询(已在此类运算符树中进行了翻译)的代码生成过程。当调用produce时,运算符会生成它们的代码,并且它们可以强制其子代生成它们的代码等等。这导致数据中心的查询代码(Thomas Neumann提出了这种数据中心的查询处理方法-http://hyper-db.de/)。 - moo
目前,我通过使用 << 将操作符的代码(在 produce 方法中)写入文件来生成它。这使得很难理解操作符的功能,因为它的代码主要由这样的文件写入组成以生成其代码。我的目标是通过某种方式隐藏代码生成,从而使操作符的功能更易于理解。 - moo
你可能不需要那么多的功能;你所要求的只是可用的和已使用的。看起来你拥有的是一个经典的递归树遍历,从根节点开始,打印(“<<”)文本字符串;这样的方案中,你无法以任何有用的方式后处理生成的答案。你可以看到DMS(以及类似的其他PTS工具)模式的样子;完全没有“打印”,只有生成代码的骨架。通过组合模式来生成树;最后一步将树作为有效文本进行漂亮的打印。请参阅引用的论文,了解一些有趣的生成器模式。 - Ira Baxter
谢谢,我会仔细看一下! - moo

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