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

相关文章

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

C#中async await异步关键字用法和异步的底层原理全解析

《C#中asyncawait异步关键字用法和异步的底层原理全解析》:本文主要介绍C#中asyncawait异步关键字用法和异步的底层原理全解析,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录C#异步编程一、异步编程基础二、异步方法的工作原理三、代码示例四、编译后的底层实现五、总结C#异步编程

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

Go 语言中的select语句详解及工作原理

《Go语言中的select语句详解及工作原理》在Go语言中,select语句是用于处理多个通道(channel)操作的一种控制结构,它类似于switch语句,本文给大家介绍Go语言中的select语... 目录Go 语言中的 select 是做什么的基本功能语法工作原理示例示例 1:监听多个通道示例 2:带

如何高效移除C++关联容器中的元素

《如何高效移除C++关联容器中的元素》关联容器和顺序容器有着很大不同,关联容器中的元素是按照关键字来保存和访问的,而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的,本文介绍了如何高效移除C+... 目录一、简介二、移除给定位置的元素三、移除与特定键值等价的元素四、移除满足特android定条件的元

鸿蒙中@State的原理使用详解(HarmonyOS 5)

《鸿蒙中@State的原理使用详解(HarmonyOS5)》@State是HarmonyOSArkTS框架中用于管理组件状态的核心装饰器,其核心作用是实现数据驱动UI的响应式编程模式,本文给大家介绍... 目录一、@State在鸿蒙中是做什么的?二、@Spythontate的基本原理1. 依赖关系的收集2.

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序