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_NAME arr_person 字段名列表,类型为 std::array<frozen::string, 2>
fields_##STRUCT_NAME fields_person 字段名列表,类型为 std::string_view
name_##STRUCT_NAME name_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, end it 指向当前解析到 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");
      }
    }
  else
    IGUANA_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 这种类型分支判断

以上

相关推荐

  1. iguana C++ 反射原理

    2024-03-17 06:18:04       41 阅读
  2. Ponder-C++反射入门

    2024-03-17 06:18:04       35 阅读
  3. C# 反射

    2024-03-17 06:18:04       37 阅读
  4. C# 反射

    2024-03-17 06:18:04       27 阅读
  5. C# 反射

    2024-03-17 06:18:04       22 阅读
  6. <span style='color:red;'>C</span>#-<span style='color:red;'>反射</span>

    C#-反射

    2024-03-17 06:18:04      17 阅读
  7. c#反射用法

    2024-03-17 06:18:04       55 阅读
  8. C# 反射基础

    2024-03-17 06:18:04       39 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-17 06:18:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-17 06:18:04       101 阅读
  3. 在Django里面运行非项目文件

    2024-03-17 06:18:04       82 阅读
  4. Python语言-面向对象

    2024-03-17 06:18:04       91 阅读

热门阅读

  1. web蓝桥杯真题:搜一搜呀

    2024-03-17 06:18:04       40 阅读
  2. Python自动化测试之使用pytest-mock模拟用户输入

    2024-03-17 06:18:04       37 阅读
  3. ubuntu 安装配置 ollama

    2024-03-17 06:18:04       39 阅读
  4. node: -max-old-space-size=xxx is not allowed in NODE_OPTIONS

    2024-03-17 06:18:04       44 阅读
  5. pandas导入list列表型数据、dict字典型数据

    2024-03-17 06:18:04       35 阅读
  6. 【Android】WebView请求HttpRequest和HttpResponse

    2024-03-17 06:18:04       34 阅读