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++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

java中反射Reflection的4个作用详解

《java中反射Reflection的4个作用详解》反射Reflection是Java等编程语言中的一个重要特性,它允许程序在运行时进行自我检查和对内部成员(如字段、方法、类等)的操作,本文将详细介绍... 目录作用1、在运行时判断任意一个对象所属的类作用2、在运行时构造任意一个类的对象作用3、在运行时判断

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性: