使用C++26静态反射在编译时解析JSON

Lobsters Hottest 工具

摘要

C++26的#embed和静态反射,结合simdjson库,允许在编译时解析JSON,将配置文件转化为编译时常量,无运行时开销。

<p><a href="https://lobste.rs/s/xqxidz/parsing_json_at_compile_time_with_c_26">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/06/15 09:05

# 在编译时使用 C++26 静态反射解析 JSON 来源:https://lemire.me/blog/2026/06/14/parsing-json-at-compile-time-with-c26-static-reflection/ 假设你有一个 JSON 格式的配置文件。看起来像这样: ```json { "width": 1920, "height": 1080, "fullscreen": true, "title": "My Game", "volume": 0.8 } ``` 通常你会把这个文件连同程序一起发布,启动时打开它、读取并解析。对于永不变化的数据来说,这工作量大得多。如果该文件在构建时就已固定呢?编译器能否读取、解析它,并将结果直接作为常量嵌入到可执行文件中? C++26 给出了肯定的答案。我们需要两个新特性,所有这些特性现在都可以在最新的 GCC 编译器(版本 16)中使用: 1. `#embed` 指令,用于在编译时将文件拉入程序; 2. 一个支持 *静态反射* 的软件库,例如 simdjson。 让我展示一下我们能走多远。新的 `#embed` 指令读取一个文件并将其展开成一个逗号分隔的字节值列表。要在编译时读取文件 `data.json` 并作为常量保留,我们可以这样写: ```cpp constexpr const char json_data[] = { #embed "data.json" , 0 }; ``` 我使用了 `constexpr`,因为我希望编译器能够在常量求值过程中检查这些字节。尾部的 `, 0` 只是附加了一个空终止符,这样该数组就可以当作普通的 C 字符串来使用。完全没有运行时 I/O。这些字节就是程序的一部分。 但是嵌入的字节本身还没有用处。我真正想要的是一个类型化的 C++ 对象。在我的示例中,目标类型是这个配置结构体: ```cpp struct Window { int width; int height; bool fullscreen; std::string title; double volume; }; ``` 传统上,从 JSON 填充这样的结构体需要为每个字段手工编写一行代码:读取 `"width"`,存储到 `width` 中;读取 `"height"`,存储到 `height` 中,以此类推。这很繁琐。而且因为它在启动时执行,一个格式错误的文件会变成运行时错误,由你的用户(而不是你)发现。 最新版本的 [simdjson](https://github.com/simdjson/simdjson) 可以使用 C++26 静态反射在 *编译时* 解析 JSON。入口点是 `simdjson::compile_time::parse_json`,它做的事情让我觉得有点神奇:它读取 JSON,并根据找到的键为你合成结构体类型。 ```cpp #define SIMDJSON_STATIC_REFLECTION 1 #include "simdjson.h" constexpr const char json_data[] = { #embed "data.json" , 0 }; constexpr auto window = simdjson::compile_time::parse_json<Window>(); ``` 变量 `window` 是一个完全由编译器计算出来的值。它的类型是从文档生成的:它有一个 `width` 和一个 `height`(都是 64 位整数),一个 `bool` 类型的 `fullscreen`,一个 `double` 类型的 `volume`,以及一个 `title`。此后我就可以写 `window.width`,它表现得就像任何普通字段一样。 我怎么知道解析真正发生在编译时?因为我可以对结果进行断言,这些断言必须在程序存在之前由编译器检查: ```cpp static_assert(window.width == 1920); static_assert(window.height == 1080); static_assert(window.fullscreen == true); ``` 如果我损坏了 JSON——删除一个花括号、拼错 `true`、留下一个尾随逗号——程序将无法编译,错误指向 `parse_json` 那一行。损坏的文件在构建时就被发现了,在我自己的机器上,而不是在别人机器上的启动时。 由于 `window` 是真正的编译时常量,任何基于它的计算也都是常量。考虑这个函数: ```cpp int screen_area() { return window.width * window.height; } ``` 用 `-O3` 编译后,没有乘法,没有字段访问,当然也没有任何解析残留——只剩下答案作为立即数(这里是在我的 MacBook 上): ```asm screen_area: mov w0, #0xa400 // 0x1fa400 = 2073600 movk w0, #0x1f, lsl #16 ret ``` JSON 已经从二进制中消失了。它被编译器读取并解析了恰好一次,唯一存活下来的就是数字 2073600。 由于静态反射非常新,在使用 GCC 16 构建时,你需要传递标志 `-std=c++26 -freflection`:`-freflection` 标志是激活编译时反射所必需的。你还必须在导入 `simdjson.h` 之前设置 simdjson 宏 `SIMDJSON_STATIC_REFLECTION=1`。这是一个临时安全措施。 重现这些示例的源代码可以在 [这里](https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/tree/master/2026/06/16) 找到。 *参考*: [P2996 — Reflection for C++26](https://wg21.link/p2996) 和 [simdjson 库](https://simdjson.org/)。 *鸣谢*:simdjson 的实现是与 Francisco Geiman Thiesen 共同完成的。

相似文章

C++26: #embed

Lobsters Hottest

C++26 introduces the #embed preprocessor directive, allowing binary files to be embedded directly at compile time without external tools, simplifying resource inclusion in C++ programs.

JSON5E - 人类友好的JSON5

Lobsters Hottest

libpdjson5 是一个公共领域的 C 语言库,用于解析 JSON、JSON5 和 JSON5E,具有完整的 Unicode 支持、极小的内存占用和流式 API。它是 pdjson 的一个分支,进行了多项改进,包括对 JSON5E 的支持。