当前位置: 首页 > news >正文

如何在实际开发中深入使用 yalantinglibs 编译期反射库

yaLanTingLibs 是阿里云开源的现代 C++ 基础工具库的集合,包括序列化、http、rpc、协程、编译期反射、metric 和日志等库,可以帮助 C++ 开发者快速构建高性能的 C++ 应用。2024 云栖大会操作系统技术 Workshop 上,阿里云智能集团高级技术专家、purecpp社区负责人、《深入应用C++11》作者祁宇分享了《深入应用C++20编译期反射》,本文将重点介绍 yaLanTingLibs 编译期反射库,通过一些案例介绍其应用场景、功能和特色,以及如何将它用于实际开发。

本文的例子推荐安装龙蜥操作系统的 Alibaba Cloud Compiler 编译器编译,安装方法见链接:安装并使用Alibaba Cloud Compiler构建高性能的C++应用_Alibaba Cloud Linux(Alinux)-阿里云帮助中心

(图/阿里云智能集团高级技术专家、purecpp社区负责人、《深入应用C++11》作者祁宇)

编译期反射一个对象

首先,从一个简单的例子开始:

#include "ylt/reflection/member_value.hpp"struct simple {int color;int id;std::string str;int age;
};int main() {using namespace ylt::reflection;// 编译期获取simple 字段个数static_assert(member_count_v<simple> == 4);// 编译期获取simple 类型的名称static_assert(get_struct_name<simple>() == "simple");// 编译期获取simple 字段名称列表static_assert(get_member_names<simple>() == std::array<std::string_view, 4>{"color", "id", "str", "age"});// 根据类型遍历其字段名和索引for_each<simple>([](std::string_view field_name) {std::cout << field_name << "\n";}); for_each<simple>([](std::string_view field_name, size_t index) {std::cout << index << ", " << field_name << "\n";});
}

除了获取这些最基本的对象元数据的 API 之外,ylt::reflection 还提供了更多有用的 API:

int main() {simple p{.color = 2, .id = 10, .str = "hello reflection", .age = 6};// 通过编译期索引获取字段值CHECK(std::get<0>(p) == 2);CHECK(std::get<2>(p) == "hello reflection");// 根据编译期索引获取字段名static_assert(name_of<simple, 1>()== "id");// 根据编译期字段名获取字段值CHECK(get<"age"_ylts>(p) == 6);CHECK(get<"str"_ylts>(p) == "hello reflection");// 根据编译期字段名获取字段索引static_assert(index_of<simple2, "str"_ylts>() == 2);// 遍历对象的字段、字段名、字段索引, 并打印for_each(p, [](auto& field, auto name, auto index) {std::cout << field << ", " << name << ", " << index << "\n";});// 访问对象的所有字段值visit_members(p, [](auto&&... args) {((std::cout << args << " "), ...);std::cout << "\n";});  
}

有了这些编译期反射的 API 之后就可以做一些有趣的事了,比如做序列化,理论上可以通过编译期反射将一个对象序列化到任意格式。事实上 yalantinglibs 也是这样做的,通过统一的编译期反射 API 将对象转换为 json,xml,yaml,protobuf 等格式。

基于编译期反射的序列化

基于 yalantinglibs 反射库可以非侵入式的将一个对象序列化到多种数据格式,也可以扩展支持自定义的格式。

#include "ylt/struct_json/json_reader.h"
#include "ylt/struct_json/json_writer.h"
#include "ylt/struct_xml/xml_reader.h"
#include "ylt/struct_xml/xml_writer.h"
#include "ylt/struct_yaml/yaml_reader.h"
#include "ylt/struct_yaml/yaml_writer.h"
#include "ylt/struct_pb.hpp"struct simple {int color;int id;std::string str;int age;
};int main() {simple p{.color = 2, .id = 10, .str = "hello reflection", .age = 6};std::string json;struct_json::to_json(p, json);std::string xml;struct_xml::to_xml(p, xml);std::string yaml;struct_yaml::to_yaml(p, yaml);std::string protobuf;struct_pb::to_pb(p, protobuf);simple p1;struct_json::from_json(p1, json);struct_xml::from_xml(p1, xml);struct_yaml::from_yaml(p1, xml);struct_pb::from_pb(p1, xml);
}

使用yalantinglibs reflection库实现自定义格式的序列化

使用 yalantinglibs reflection 库可以很方便的将对象序列化到自定义格式,比如将一个对象序列化成一个简单的 json5 格式,json5 的 key 是没有引号的。

只需要通过反射 API for_each 拿到对象的字段值和字段名就够了。

struct point {int x;int y;
};void test_json5() {point pt{2, 4};std::string json5;json5.append("{");ylt::reflection::for_each(pt, [&](auto& field, auto name) {json5.append(name).append(":").append(std::to_string(field)).append(",");});json5.back() = '}';CHECK(json5 == "{x:2,y:4}");
}

低版本编译器的支持

上述中的 example 只能在 C++20 高版本编译器(clang13+, gcc11+, msvc2022)中才能编译运行。如果编译器版本较低只支持 C++17 能用 yalantinglibs 的反射库吗?

答案也是肯定的,yalantinglibs 的反射库兼容了 C++17,支持的最低版本是 gcc9,不过需要宏定义一个额外的宏才能使用反射 API(C++20 高版本编译器中是不需宏的),比如之前的例子:

struct simple {int color;int id;std::string str;int age;
};
YLT_REFL(simple, color, id, str, age);int main() {using namespace ylt::reflection;// 编译期获取simple 字段个数static_assert(member_count_v<simple> == 4);// 编译期获取simple 类型的名称static_assert(get_struct_name<simple>() == "simple");// 编译期获取simple 字段名称列表static_assert(get_member_names<simple>() == std::array<std::string_view, 4>{"color", "id", "str", "age"});// 根据类型遍历其字段名和索引for_each<simple>([](std::string_view field_name, size_t index) {std::cout << index << ", " << field_name << "\n";});
}

定义了 YLT_REFL 宏之后就可以使用反射的 API 了,如果对象的字段都是私有的,则需要将宏定义到对象内部:

struct simple {
YLT_REFL(simple, color, id, str, age);
private:int color;int id;std::string str;int age;
};

这种方式可以反射私有字段但有侵入性,如果对象有 public 的访问私有字段的方法也可做到非侵入式:

struct dummy_t5 {private:int id = 42;std::string name = "tom";int age = 20;public:int& get_id() { return id; }std::string& get_name() { return name; }int& get_age() { return age; }const int& get_id() const { return id; }const std::string& get_name() const { return name; }const int& get_age() const { return age; }
};
YLT_REFL(dummy_t5, get_id(), get_name(), get_age());

如果对象字段都是私有的,并且没有提供 public 的访问方法还能反射吗?也是可以的,可以通过 yalantinglibs reflection 另外一个宏 YLT_REFL_PRIVATE 非侵入式的反射对象的私有字段:

class private_struct {int a;int b;public:private_struct(int x, int y) : a(x), b(y) {}
};
YLT_REFL_PRIVATE(private_struct, a, b);

现在就可以反射对象的私有字段了:

  private_struct st(2, 4);for_each(st, [](auto& field, auto name, auto index){std::cout << field << ", " << name << ", " << index << "\n";});visit_members(st, [](auto&... args) {((std::cout << args << " "), ...);std::cout << "\n";});  

总结

编译期反射的应用场景很广泛,非常适合用在序列化场景、ORM(实体-映射)场景、非侵入式访问对象等场景,yalantinglibs 的反射库在支持 C++20 的同时也兼容了 C++17,不论是低版本编译器还是高版本编译器,或者对象存在私有字段等场景,都能使用统一的一套易用的 API。

—— 完 ——


http://www.mrgr.cn/news/39159.html

相关文章:

  • GPT与大模型行业落地实践探索
  • Java SE 总结
  • 如何让虚拟机与本地电脑使用同一个ip
  • 【读书笔记-《30天自制操作系统》-25】Day26
  • 小学生为什么要学英语
  • BaoStock 的安装
  • B3621 枚举元组
  • Matlab实现麻雀优化算法优化回声状态网络模型 (SSA-ESN)(附源码)
  • 【热门主题】000003 案例 ECMAScript 标准:JavaScript 的基石与未来
  • vue3中动态引入组件并渲染组件
  • 电子信息工程职称评审流程有哪些?
  • 点餐小程序实战教程13餐桌管理
  • IDEA关联Tomcat
  • 【算法】反向传播算法
  • 【高效管理集合】并查集的实现与应用
  • 【IoT-NTN】系统消息SIB32信令分析
  • Matlab|考虑阶梯式碳交易与供需灵活双响应的综合能源系统优化调度
  • 985官宣:19名本科生,获国自然项目!
  • C语言-Linux进程间通信方式
  • Web安全 - 跨站点请求伪造CSRF(Cross Site Request Forgery)