【问题标题】:How to use meta-programming to generate code?如何使用元编程来生成代码?
【发布时间】:2017-11-18 09:02:35
【问题描述】:

问题: 在我的系统中,有很多统计计算器被包装成类。我的工作是将这些计算器的值组合成一个名为“总计”的值。

解释如何组合这些计算器的“模型”以表格形式给出 --- csv 文件或 MySQL 中的表格

instance_name,accessor,argument,post_processing_function_name
fleet_statistics,getCash,2017,func1
city_statistics,getPopulation,2016,func2
....
....

我目前的解决方案:我编写了一个 python 脚本来读取 csv 文件并生成这样的 c++ 代码:

double computeTotal()
{
    double total = 0;
    total += func1(fleet_statistics->getCash(2017));
    total += func2(city_statistics->getPopulation(2016));
    ....
    ....
}

使用python生成代码很方便,但问题是,它不再是一个“纯”的c++程序。我需要修改 makefile 以确保生成的 c++ 文件是最新的。

更优雅的解决方案是使用元编程在 c++ 中生成代码,使用 c++ 而不是 python。谁能告诉我如何做到这一点?

只提几点:

  1. CSV 文件是由一些与业务逻辑相关的脚本生成的。它有几千行。手动编写c++代码是不可能的。

  2. 每次发布​​新版本时,CSV 文件都会发生变化。

  3. 我以 CSV 格式为例。如果这会让事情变得更容易,它可能是 MySQL 中的一个表

  4. instance_name 列中的对象不一定来自同一个 BaseClass。访问器的函数签名也是变体的。所以用函数指针不方便

谢谢!

【问题讨论】:

  • 你能改变输入 csv 的格式,还是给定的?
  • 这感觉像是一个 XY 问题。你真的需要元编程吗?为什么不定义一个包含一些函数指针的 C++ 数据结构呢?或者为什么不直接手动编写代码?您是在询问有关您提出的解决方案的问题,而不是真正说出问题所在。
  • 更重要的是,您为什么要更改现有的解决方案? makefile 中包含的代码生成有什么问题?为什么称其为“黑客”?可能很难调试,这是肯定的,但它工作正常吗? “纯 C++”是一个毫无意义的要求。为艺术而艺术。
  • 为什么这个 CSV 文件首先存在?如果有人每周对其进行编辑,他们就不能改为编辑 C++ 代码吗?
  • 重复 Davids 的问题,为什么需要元编程?据我所见,您需要一个数据库。硬编码一千行数据听起来是错误的。尤其是它经常变化。每次使用程序时都不应要求重新编译。

标签: c++ templates metaprogramming


【解决方案1】:

当 CSV 文件发生变化时,应该不需要生成和编译 C++ 代码。

您只需编写解析 CSV 文件的 C++ 代码一次,通过在 C++ 对象上调用 C++ 函数来执行 CSV 文件中指定的计算。

所以,粗略地说,您的fleet_statisticscity_statistics 类应该实现一些具有double getYearValueByName( String name, int year ) 函数的通用接口,该函数检查给定名称并在自身上调用同名的相应函数以获取值那一年。

我知道这种方法很简单,实际上事情比这更复杂;您需要继续扩展该模型,使其在必要时变得更加复杂,直到您拥有解析 CSV 文件所需的所有功能并按需要执行。

【讨论】:

  • 通常正确,但在某些情况下,在运行时生成 C++ 代码确实有意义。看我的回答。
【解决方案2】:

您在 cmets 中提到输入文件的格式在您的控制之下。使用实际元编程(更具体地说是预处理器元编程)的一种方法是将文件的格式更改为适合使用X macro 模式的格式。像这样的:

DATA_POINT(fleet_statistics,getCash,2017,func1)
DATA_POINT(city_statistics,getPopulation,2016,func2)

然后,在你的 C++ 代码中,你会这样做:

double computeTotal()
{
    double total = 0;
    #define DATA_POINT(instance_name,accessor,argument,post_processing_function_name) \
      total += post_processing_function_name(instance_name->accessor(argument));
    #include "datafile"
    #undef DATA_POINT
}

这样,正在编译的 C++ 代码是由预处理器从数据文件中生成的。

但是,您绝对应该考虑其他 cmets 和答案(我分享)中给出的意见,解析器可能更适合您的情况。通常,将代码与数据分开是个好主意。

【讨论】:

  • 这正是我想要的。我遥远地记得大多数预处理器元编程都可以转换为模板元编程(模板元编程最初是为了改进宏而设计的)你认为可以使用模板来实现吗?
  • @user152503 为什么?您在这里拥有的本质上是文本操作,这是预处理器(或其他宏处理器)所擅长的。模板用于类型和值操作。
  • @user152503 但是,我仍然不相信元编程在这里比普通的运行时解析器更合适。您提到“instance_name 列中的对象不一定来自同一个 BaseClass”。是什么阻止您向他们引入额外的界面?
  • 可能有几十个独立的类(非派生类),每个类都有十几个具有不同签名的访问器。如果我构建一个所有这些类都派生自的通用接口,那么这个接口可能会很大——不确定这是否是一个好的设计
【解决方案3】:

但问题是,它不再是“纯”c++

所以?这是一个真正的问题吗?显然,您的情况比简单的“读取 csv 并获取总和”更复杂。这需要更复杂的解决方案。但不要让它们变得比需要的更复杂。

您使用的技术(即通过其他脚本生成 C++ 代码)没有任何问题。虽然不常见,但您仍然可以通过 Google's protobuf 的著名示例在其他地方看到它。

因此,与其寻找其他复杂的解决方案(Jit 编译?真的吗?甚至模板通常也很难遵循),不如问问自己:我的 Python 代码生成是否有效?它的表现是否足够好?容易维护吗?重构的成本是多少?这些都是真正的问题。

另外请注意,虽然 makefile 主要与 C/C++ 一起使用,但它们并不受严格限制。实际上,任何使用一种或多种语言的构建过程都可以用它们来完成。你可以混合它们。这不是黑客行为。这只是一个构建过程。

【讨论】:

  • 我要补充一点:确保记录所有这些。如果您的构建过程有据可查,并且最好使用非晦涩难懂的工具和语言,那么应该没有问题。
【解决方案4】:

首先,您想为 CSV 文件中的公式编写一些解释器。你可能会发现一个图书馆正是这样做的。否则,您需要实现这样的interpreter(或嵌入一些现有的,如LuaGuile)。绝对阅读一些好书,例如Dragon BookSICP 然后Lisp In Small Pieces。您需要代表ASTs 和环境(用于变量绑定)。如果您从未学习过编译器和解释器,您确实需要花费数周时间阅读这些内容(这些技术很成熟但很困难,因此您无法重新发明所有这些)。

(我猜你有一个很好的操作系统,比如 Linux。如果没有,请调整我的答案)

如果性能真的很重要,因为 CSV 文件中的公式很复杂并且需要时间来计算,请考虑使用一些 JIT compilation 库(例如 libgccjit)将您的 AST(来自公式)转换为某种形式的代码。

在极少数情况下(但可能不是您的情况),您可能会在运行时将一些 C++ 生成到某个临时文件中,然后将其编译为 plugindynamically load 那个临时插件(例如,使用 dlopen & dlsym 在 Linux 上)。通常这是不值得的,因为 C++ 编译器真的很慢(在编译时!)。经常生成 C 代码的是more appropriate(但 YMMV)。但编译为 C 或 C++ 绝非易事,需要数月的开发时间。

我需要修改 makefile 以确保生成的 c++ 文件是最新的。

不一定(需要破解Makefile,这没什么大不了的)。您可以从 Python 脚本或 C++ 程序运行 g++(或您的 C++ 编译器,如果不是 GCC)。我不知道(在您的具体情况下)这样做是否明智。

注意。请注意,在 C++ 的上下文中,元编程意味着(通常)不生成 C++ 代码或其他代码,而是指 template metaprogramming

【讨论】:

    猜你喜欢
    • 2018-03-24
    • 2011-02-07
    • 1970-01-01
    • 2010-10-02
    • 1970-01-01
    • 1970-01-01
    • 2022-06-10
    • 1970-01-01
    相关资源
    最近更新 更多