iguana 库 C++ 反射原理

2024-03-17 05:12
文章标签 c++ 原理 反射 iguana

本文主要是介绍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 这种类型分支判断

以上

这篇关于iguana 库 C++ 反射原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/817916

相关文章

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ

C++中RAII资源获取即初始化

《C++中RAII资源获取即初始化》RAII通过构造/析构自动管理资源生命周期,确保安全释放,本文就来介绍一下C++中的RAII技术及其应用,具有一定的参考价值,感兴趣的可以了解一下... 目录一、核心原理与机制二、标准库中的RAII实现三、自定义RAII类设计原则四、常见应用场景1. 内存管理2. 文件操

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

Python中使用uv创建环境及原理举例详解

《Python中使用uv创建环境及原理举例详解》uv是Astral团队开发的高性能Python工具,整合包管理、虚拟环境、Python版本控制等功能,:本文主要介绍Python中使用uv创建环境及... 目录一、uv工具简介核心特点:二、安装uv1. 通过pip安装2. 通过脚本安装验证安装:配置镜像源(可

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

C++作用域和标识符查找规则详解

《C++作用域和标识符查找规则详解》在C++中,作用域(Scope)和标识符查找(IdentifierLookup)是理解代码行为的重要概念,本文将详细介绍这些规则,并通过实例来说明它们的工作原理,需... 目录作用域标识符查找规则1. 普通查找(Ordinary Lookup)2. 限定查找(Qualif

Java反射实现多属性去重与分组功能

《Java反射实现多属性去重与分组功能》在Java开发中,​​List是一种非常常用的数据结构,通常我们会遇到这样的问题:如何处理​​List​​​中的相同字段?无论是去重还是分组,合理的操作可以提高... 目录一、开发环境与基础组件准备1.环境配置:2. 代码结构说明:二、基础反射工具:BeanUtils

Mysql的主从同步/复制的原理分析

《Mysql的主从同步/复制的原理分析》:本文主要介绍Mysql的主从同步/复制的原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录为什么要主从同步?mysql主从同步架构有哪些?Mysql主从复制的原理/整体流程级联复制架构为什么好?Mysql主从复制注意