iguana 库 C++ 反射原理

iguana

Github : https://github.com/fananchong/iguana

官方介绍: universal serialization engine

虽然官方介绍是通用的序列化引擎,但实际上目前只支持:

  • json
  • yaml
  • xml

不过, C++ 结构体/类的反射部分是通用的

通过该库,可以学习到使用宏和模板实现 C++ 反射的一种方法

iguana 用法示例

先简单看下 iguana 如何实现:

以下代码摘自 https://github.com/qicosmos/iguana?tab=readme-ov-file#tutorial

定义 person :

struct person
{std::string  name;int          age;
};
REFLECTION(person, name, age) //define meta data

序列化为 json 字符串:

person p = { "tom", 28 };
iguana::string_stream ss; // here use std::string is also ok
iguana::to_json(p, ss);
std::cout << ss.str() << std::endl; 

从 json 字符串,反序列化回 person

std::string json = "{ \"name\" : \"tom\", \"age\" : 28}";
person p;
iguana::from_json(p, json);

以上例子中,通过REFLECTION(person, name, age),在编译期,反射 person 相关字段信息

运行态,可以利用这些反射的信息,做to_jsonfrom_json

REFLECTION 宏分析

REFLECTION 宏定义如下:

#define REFLECTION(STRUCT_NAME, ...)                                    \MAKE_META_DATA(STRUCT_NAME, #STRUCT_NAME, GET_ARG_COUNT(__VA_ARGS__), \__VA_ARGS__)#define MAKE_META_DATA(STRUCT_NAME, TABLE_NAME, N, ...)                       \static constexpr inline std::array<frozen::string, N> arr_##STRUCT_NAME = { \MARCO_EXPAND(MACRO_CONCAT(CON_STR, N)(__VA_ARGS__))};                   \static constexpr inline std::string_view fields_##STRUCT_NAME = {           \MAKE_NAMES(__VA_ARGS__)};                                               \static constexpr inline std::string_view name_##STRUCT_NAME = TABLE_NAME;   \MAKE_META_DATA_IMPL(STRUCT_NAME,                                            \MAKE_ARG_LIST(N, &STRUCT_NAME::FIELD, __VA_ARGS__))#define MAKE_META_DATA_IMPL(STRUCT_NAME, ...)                                 \[[maybe_unused]] inline static auto iguana_reflect_members(                 \STRUCT_NAME const &) {                                                  \struct reflect_members {                                                  \constexpr decltype(auto) static apply_impl() {                          \return std::make_tuple(__VA_ARGS__);                                  \}                                                                       \using size_type =                                                       \std::integral_constant<size_t, GET_ARG_COUNT(__VA_ARGS__)>;         \constexpr static std::string_view name() { return name_##STRUCT_NAME; } \constexpr static std::string_view struct_name() {                       \return std::string_view(#STRUCT_NAME, sizeof(#STRUCT_NAME) - 1);      \}                                                                       \constexpr static std::string_view fields() {                            \return fields_##STRUCT_NAME;                                          \}                                                                       \constexpr static size_t value() { return size_type::value; }            \constexpr static std::array<frozen::string, size_type::value> arr() {   \return arr_##STRUCT_NAME;                                             \}                                                                       \};                                                                        \return reflect_members{};                                                 \}

REFLECTION(person, name, age)展开:

static constexpr inline std::array<frozen::string, 2> arr_person = {std::string_view("name", sizeof("name") - 1),std::string_view("age", sizeof("age") - 1),
};
static constexpr inline std::string_view fields_person = {"name, age",
};
static constexpr inline std::string_view name_person = "person";
[[maybe_unused]] inline static auto iguana_reflect_members(person const &) {struct reflect_members {constexpr decltype(auto) static apply_impl() {return std::make_tuple(&person::name, &person::age);}using size_type = std::integral_constant<size_t, 2>;constexpr static std::string_view name() { return name_person; }constexpr static std::string_view struct_name() {return std::string_view("person", sizeof("person") - 1);}constexpr static std::string_view fields() { return fields_person; }constexpr static size_t value() { return size_type::value; }constexpr static std::array<frozen::string, size_type::value> arr() {return arr_person;}};return reflect_members{};
}

从宏展开代码可以看到, REFLECTION 定义可以得到 person 的以下元信息:

元信息的宏定义person说明
arr_##STRUCT_NAMEarr_person字段名列表,类型为 std::array<frozen::string, 2>
fields_##STRUCT_NAMEfields_person字段名列表,类型为 std::string_view
name_##STRUCT_NAMEname_person结构体/类名
iguana_reflect_members(STRUCT_NAME const &){}iguana_reflect_members(person const &)元数据信息。通过调用 iguana_reflect_members 返回 reflect_members 结构体

reflect_members 元数据结构体,除了上面表格中列的内容,还提供了:

方法元数据说明
apply_impl()字段地址列表,类型 std::tuple实现反射的关键。通过它结合结构体/类实例,获取结构体/类实例字段的值或赋值
value()字段个数

from_json 实现

from_json 函数实现在iguana/json_reader.hpp, 504 行 - 599 行

把一些边界代码、遍历代码去掉,核心逻辑如下:

  std::string_view key = detail::get_key(it, end);static constexpr auto frozen_map = get_iguana_struct_map<T>();const auto &member_it = frozen_map.find(key);std::visit([&](auto &&member_ptr) IGUANA__INLINE_LAMBDA {from_json_impl(value.*member_ptr, it, end);},member_it->second);

这段代码的意思是:

代码说明
it, endit 指向当前解析到 json 字符串的位置; end json 串结尾位置
key = detail::get_key(it, end)获取字段名
frozen_map = get_iguana_struct_map<T>()获取一个 map ,该 map key 为字段名;值为 std::variant 类型的字段地址
上面提到 apply_impl() 返回字段地址列表,类型 std::tuple
get_iguana_struct_map 函数就是把 std::tuple 类型的内容,编译期转成 std::variant 类型
std::visit对 std::variant 类型对象做访问
from_json_impl(value.*member_ptr, it, end)from_json_impl 是个模板,不同类型都有特化实现
it, end 获得 value 值,转化为对应类型赋值 value.*member_ptr

from_json 过程思路很清晰,就是把 key - value (字段,字段值),填充到对象上

从 apply_impl() 得到的字段地址列表,实际上已经可以实现这个思路

iguana 在实现上,考虑到编码的简洁,引入了 std::visit - std::variant 编程技巧

对每种类型的解析赋值过程,均对应一个 from_json_impl 类型特化的模板函数

这样就不会有 if else 颓长的类型判断代码

同时,成员字段也可能是需要反射的类型,那么 from_json_impl 类型特化的模板函数也实现一个,就可以实现递归解析了:

template <typename U, typename It, std::enable_if_t<refletable_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {from_json(value, it, end);
}

再举例解析 bool 类型值如下:


template <typename U, typename It, std::enable_if_t<bool_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &&value, It &&it, It &&end) {skip_ws(it, end);if (it < end)IGUANA_LIKELY {switch (*it) {case 't':++it;match<'r', 'u', 'e'>(it, end);value = true;break;case 'f':++it;match<'a', 'l', 's', 'e'>(it, end);value = false;break;IGUANA_UNLIKELY default: throw std::runtime_error("Expected true or false");}}elseIGUANA_UNLIKELY { throw std::runtime_error("Expected true or false"); }
}

to_json 函数实现,思路类似,不再复述

总结

iguana 实现反射思考:

  • 通过定义 REFLECTION 宏,在编译期,生成结构体/类的元数据信息
    • 字段名列表
    • 字段地址列表
    • 将字段地址列表做成 std::tuple
    • 将该 std::tuple 做成 std::map , 其 key 为字段名,其值为 std::variant 类型字段地址
  • 不同格式的序列化、反序列,最终要通过字段名给对象的字段赋值或取值
    • 通过 std::visit - std::variant 编程技巧
    • 使用函数类型特化方式,避免 if else 这种类型分支判断

以上

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/750024.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 自动备份 mysql 脚本

这个脚本会将数据库备份为一个SQL文件&#xff0c;并将其保存在指定的目录中。 #!/bin/bash# MySQL配置 DB_USER"your_mysql_username" DB_PASS"your_mysql_password" DB_NAME"your_database_name" DB_HOST"localhost"# 备份目录 BAC…

Spring核心方法:Refresh全解(WebMVC如何装配、关联)

Spring核心方法&#xff1a;Refresh全解(WebMVC如何装配、关联) 这里是一个表格&#xff0c;列出了Spring容器刷新过程中执行的方法以及它们的作用&#xff1a; 方法名称描述prepareRefresh()初始化一些属性和状态&#xff0c;例如启动时间戳、活动标志、环境变量等。obtainF…

HTML-DAY2

HTML-DAY2 表格标签 表格的主要作用 表格主要用于显示、展示数据&#xff0c;因为它可以让数据显示的非常的规整&#xff0c;可读性非常好。特别是后台展示数据的时候&#xff0c;能够熟练运用表格就显得很重要。一个清爽简约的表格能够把繁杂的数据表现得很有条理。 Pink老…

Python的Selenium库中的模块、类和异常的汇总

这些是 Selenium Python 库中的模块、类和异常&#xff0c;用于实现自动化 Web 浏览器测试和网页操作。以下是它们的简单解释&#xff1a;Python Module Index — Selenium 4.18.1 documentation 1. selenium.common.exceptions&#xff1a;包含了 Selenium 中可能出现的异常。…

一款比 K8S 更好用的编排工具——Nomod

今天给笔友们推荐一款最近发现的服务编排工具Nomad。综合感觉就是功能很强大&#xff0c;姿势很优雅&#xff0c;相比 K8S 更加轻量级&#xff0c;相比 Docker-Compose 能轻松支持分布式。 Nomad 能做什么&#xff1f; Nomad 采用统一的工作流程&#xff0c;既可以轻松部署和管…

python自动化之(django)(2)

1、创建应用 python manage.py startapp apitest 这里还是从上节开始也就是命令行在所谓的autotest目录下来输入 然后可以清楚的看到 多了一个文件夹 2、创建视图 在views中加入test函数&#xff08;所建应用下&#xff09; from django.http import HttpResponse def tes…

C/C++蓝桥杯之卡片问题

问题描述&#xff1a;小兰有很多数字卡片&#xff0c;每张卡片上都是数字0&#xff5e;9。 小兰&#xff0c;准备用这些卡片拼一些数。她想从1开始拼出正整数&#xff0c;每拼一个就保存起来&#xff0c;卡片就不能再用来拼其他数了。 小兰想知道自己能从1拼到多少。 例如&a…

在Swift中集成Socket.IO进行实时通信

在Swift中集成Socket.IO进行实时通信 实时通信是许多现代应用程序的重要组成部分&#xff0c;从聊天应用程序到协作平台。Socket.IO 是一个流行的库&#xff0c;用于在 Web 和移动应用程序中实现实时的双向通信。在本文中&#xff0c;我们将讨论如何使用 Socket.IO-Client-Swi…

Rust的所有权和生命周期机制的本质

目录 所有权机制生命周期机制Rust的堆和栈堆和栈内存分配方式内存管理方式内存分配速度内存大小限制 Rust的所有权和生命周期机制的本质是关于数据在堆和栈中的存储方式。这两个概念是为了解决内存管理和资源分配的问题。 所有权机制 所有权机制确保每个值都有唯一的所有者&a…

基于CNN多阶段图像超分+去噪(超级简单版)

这是之前的一项工作&#xff0c;非常简单&#xff0c;简单的复现了两个算法&#xff0c;然后把它们串起来了。 可执行的程序链接&#xff1a;CSDN; Github 我们分成两部分进行讲解&#xff1a; 1. 图像去噪 1.1 基本思路 图像的去噪工作基于很普通的CNN去噪&#xff0c;效…

Swift 面试题及答案整理,最新面试题

Swift 中如何实现单例模式&#xff1f; 在Swift中&#xff0c;单例模式的实现通常采用静态属性和私有初始化方法来确保一个类仅有一个实例。具体做法是&#xff1a;定义一个静态属性来存储这个单例实例&#xff0c;然后将类的初始化方法设为私有&#xff0c;以阻止外部通过构造…

Ubuntu Desktop - gnome-calculator (计算器)

Ubuntu Desktop - gnome-calculator [计算器] 1. Ubuntu Software -> gnome-calculator -> Install -> Continue2. Search your computer -> Calculator -> Lock to LauncherReferences 1. Ubuntu Software -> gnome-calculator -> Install -> Continu…

STM32中MicroLIB的关闭为什么会导致卡死----解析

STM32MicroLIB 大家好我是 MHZ 。最近又开始往回捡单片机的知识了~ 之前大学的时候都没用过 STM 的 CubeMX&#xff0c;这会拿来用着感觉很方便啊~ 果然科技在进步&#xff01; 在开发使用 Keil 对 STM32 进行开发的时候在会有一个叫做 MicroLIB 的选项。 这个的具体原因我搜…

C语言 内存函数

目录 前言 一、memcpy()函数 二、memmove()函数 三、memset函数 四、memcmp()函数 总结 前言 在C语言中内存是我们用来存储数据的地址&#xff0c;今天我们来讲一下C语言中常用的内存函数。 一、memcpy()函数 memcpy()函数与我们之前讲的strcpy()函数类似&#xff0c;只…

Android FrameWork 学习路线

目录 前言 学习路线&#xff1a; 1.基础知识 2、AOSP 源码学习 3. AOSP 源码编译系统 4. Hal与硬件服务 5.基础组件 6. Binder 7. 系统启动过程分析 8. 应用层框架​编辑 9. 显示系统 10. Android 输入系统 11. 系统应用 前言 Android Framework 涉及的行业相当广…

Django 解决新建表删除后无法重新创建等问题

Django 解决新建表删除后无法重新创建等问题 问题发生描述处理办法首先删除了app对应目录migrations下除 __init__.py以外的所有文件:然后&#xff0c;删除migrations中关于你的app的同步数据数据库记录最后&#xff0c;重新执行迁移插入 问题发生描述 Django创建的表&#xf…

HCIP—OSPF课后练习一

本实验模拟了一个企业网络场景&#xff0c;R1、R2、R3为公司总部网络的路由器&#xff0c;R4、R5分别为企业分支机构1和分支机构2的路由器&#xff0c;并且都采用双上行方式与企业总部相连。整个网络都运行OSPF协议&#xff0c;R1、R2、R3之间的链路位于区域0&#xff0c;R4与R…

ubuntu开机启动时,如何随着ubuntu的启动指定qt程序使用普通用户运行?

要在Ubuntu开机启动时指定QT程序使用普通用户运行&#xff0c;您可以通过编辑/etc/xdg/autostart目录下的.desktop文件来实现。以下是详细步骤&#xff1a; 创建启动项&#xff1a;在/etc/xdg/autostart目录下创建一个新的.desktop文件。这个文件将包含启动您的QT程序的命令。…

uniapp 利用uni-list 和 uni-load-more 组件上拉加载列表

列表的加载动作&#xff0c;在移动端开发中随处可见&#xff0c;笔者也是经常用到。今天正好有空&#xff0c;做一个总结&#xff0c;方便以后使用。uniapp 利用uni-list 和 uni-load-more 组件上拉加载列表操作步骤如下&#xff1a;1、资料准备 1&#xff09;、uni-load-more…

JavaWeb--HTML

一&#xff1a;HTML简介 *HTML是一门语言&#xff0c;所有的网页都是用HTML这门语言编写出来的&#xff1b; *HTML&#xff1a;超文本标记语言&#xff1b; 超文本&#xff1a;超越了文本的限制&#xff0c;比普通文本更强大。除了文字信息&#xff0c;还能定义图片&#xff…