diff --git a/website/.vitepress/config/zh_data.ts b/website/.vitepress/config/zh_data.ts index 83bb98940..8e482a70d 100644 --- a/website/.vitepress/config/zh_data.ts +++ b/website/.vitepress/config/zh_data.ts @@ -39,6 +39,10 @@ export const metric_Links = [ { text: 'metric简介', link: '/zh/metric/metric_introduction' }, ]; +export const reflection_Links = [ + { text: 'reflection简介', link: '/zh/reflection/reflection_introduction' }, +]; + export const aboutLinks = [ { text: 'purecpp', link: '/zh/about/community' }, //TODO 未来支持英语 { text: 'contribute', link: '/zh/about/contribute' }, diff --git a/website/.vitepress/config/zh_locale.ts b/website/.vitepress/config/zh_locale.ts index a057950a2..f4574e609 100644 --- a/website/.vitepress/config/zh_locale.ts +++ b/website/.vitepress/config/zh_locale.ts @@ -16,6 +16,7 @@ export const zh_themeConfig = { text: 'coro_http', items: data.coro_http_Links }, { text: 'struct_xxx', items: data.struct_xxx_Links }, { text: 'metric', items: data.metric_Links }, + { text: 'reflection', items: data.reflection_Links }, ] }; diff --git a/website/docs/zh/guide/what_is_yalantinglibs.md b/website/docs/zh/guide/what_is_yalantinglibs.md index dad17ee3a..6cd7cd436 100644 --- a/website/docs/zh/guide/what_is_yalantinglibs.md +++ b/website/docs/zh/guide/what_is_yalantinglibs.md @@ -10,7 +10,7 @@ [English Version](../../en/guide/what_is_yalantinglibs.md) -yaLanTingLibs 是一个现代C++基础工具库的集合, 现在它包括 struct_pack, struct_json, struct_xml, struct_yaml, struct_pb, easylog, coro_rpc, coro_io, coro_http, metric 和 async_simple, 目前我们正在开发并添加更多的新功能。 +yaLanTingLibs 是一个现代C++基础工具库的集合, 现在它包括 struct_pack, struct_json, struct_xml, struct_yaml, struct_pb, easylog, coro_rpc, coro_io, coro_http, metric, reflection 和 async_simple, 目前我们正在开发并添加更多的新功能。 yaLanTingLibs 的目标: 为C++开发者提供高性能,极度易用的现代C++基础工具库, 帮助用户构建高性能的现代C++应用。 @@ -434,6 +434,10 @@ yalantinglibs工程自身支持如下配置项,如果你使用cmake find_packa 无依赖。 +### reflection + +无依赖 + ## 独立子仓库 coro_http 由独立子仓库实现: [cinatra](https://github.com/qicosmos/cinatra) diff --git a/website/docs/zh/index.md b/website/docs/zh/index.md index bf815f29b..7f61930a9 100644 --- a/website/docs/zh/index.md +++ b/website/docs/zh/index.md @@ -26,6 +26,8 @@ features: details: 基于C++20 协程的 http(https) server 和 client, 包括 get/post, websocket, multipart file upload, chunked and ranges download etc. - title: easylog details: C++17 实现的高性能易用的日志库, 支持cout 流式、sprintf 和 fmt::format/std::format 输出. + - title: reflection + details: C++17/C++20 实现的编译期反射库,提供了易用、丰富的反射API. - title: struct_xml struct_json struct_yaml details: C++17 实现的高性能易用的序列化库, 支持xml, json和yaml 的序列化/反序列化. - title: metric diff --git a/website/docs/zh/reflection/reflection.png b/website/docs/zh/reflection/reflection.png new file mode 100644 index 000000000..29409bcd3 Binary files /dev/null and b/website/docs/zh/reflection/reflection.png differ diff --git a/website/docs/zh/reflection/reflection_introduction.md b/website/docs/zh/reflection/reflection_introduction.md new file mode 100644 index 000000000..605176c60 --- /dev/null +++ b/website/docs/zh/reflection/reflection_introduction.md @@ -0,0 +1,236 @@ +yalantinglibs 的编译期反射库 + +yalantinglibs的编译期反射库提供了统一的访问对象元数据的API,让编译期反射变得简单,支持的特性也很丰富,使用C++20的时候尤其方便。 + +# 编译期反射一个对象 +从一个简单的例子开始: +```cpp +#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 == 4); + + // 编译期获取simple 类型的名称 + static_assert(get_struct_name() == "simple"); + + // 编译期获取simple 字段名称列表 + static_assert(get_member_names() == std::array{"color", "id", "str", "age"}); + + // 根据类型遍历其字段名和索引 + for_each([](std::string_view field_name) { + std::cout << field_name << "\n"; + }); + + for_each([](std::string_view field_name, size_t index) { + std::cout << index << ", " << field_name << "\n"; + }); +} +``` + +除了获取这些最基本的对象元数据的API之外,ylt::reflection还提供了更多有用的API: + +```cpp +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()== "id"); + + // 根据编译期字段名获取字段值 + CHECK(get<"age"_ylts>(p) == 6); + CHECK(get<"str"_ylts>(p) == "hello reflection"); + + // 根据编译期字段名获取字段索引 + static_assert(index_of() == 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等格式。 + +![img](reflection.png) + +# 基于编译期反射的序列化 +基于yalantinglibs 反射库可以非侵入式的将一个对象序列化到多种数据格式,也可以扩展支持自定义的格式。 + +```cpp +#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 拿到对象的字段值和字段名就够了。 + +```cpp +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 高版本编译器中是不需宏的),比如之前的例子: + +```cpp +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 == 4); + + // 编译期获取simple 类型的名称 + static_assert(get_struct_name() == "simple"); + + // 编译期获取simple 字段名称列表 + static_assert(get_member_names() == std::array{"color", "id", "str", "age"}); + + // 根据类型遍历其字段名和索引 + for_each([](std::string_view field_name, size_t index) { + std::cout << index << ", " << field_name << "\n"; + }); +} +``` +定义了YLT_REFL 宏之后就可以使用反射的API了,如果对象的字段都是私有的,则需要将宏定义到对象内部: +```cpp +struct simple { +YLT_REFL(simple, color, id, str, age); +private: + int color; + int id; + std::string str; + int age; +}; +``` +这种方式可以反射私有字段但有侵入性,如果对象有public的访问私有字段的方法也可做到非侵入式: + +```cpp +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非侵入式的反射对象的私有字段: +```cpp +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); +``` + +现在就可以反射对象的私有字段了: + +```cpp + 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。 diff --git a/website/docs/zh/struct_xxx/struct_xxx_introduction.md b/website/docs/zh/struct_xxx/struct_xxx_introduction.md index 2e57ecfa8..4463fe5f7 100644 --- a/website/docs/zh/struct_xxx/struct_xxx_introduction.md +++ b/website/docs/zh/struct_xxx/struct_xxx_introduction.md @@ -23,7 +23,7 @@ include_directories(include/ylt/thirdparty) gcc9+、clang11+、msvc2019+ # json 序列化/反序列化 -序列化需要先定义一个可反射的对象,通过REFLECTION 可以轻松定义一个可反射对象。 +序列化需要先定义一个可反射的对象,通过YLT_REFL 可以轻松定义一个可反射对象。 ```cpp struct person @@ -33,7 +33,7 @@ struct person }; YLT_REFL(person, name, age); // 通过这个宏定义元数据让person 成为一个可反射对象 ``` -通过REFLECTION 宏定义元数据之后就可以一行代码实现json 的序列化与反序列化了。 +通过YLT_REFL 宏定义元数据之后就可以一行代码实现json 的序列化与反序列化了。 ```cpp person p = { "tom", 28 }; @@ -245,7 +245,14 @@ public: person() = default; person(std::shared_ptr id, std::unique_ptr name) : id_(id), name_(std::move(name)) {} - REFLECTION_ALIAS(person, "rootnode", FLDALIAS(&person::id_, "id"), FLDALIAS(&person::name_, "name")); + static constexpr auto get_alias_field_names(person*) { + return std::array{field_alias_t{"id", 0}, field_alias_t{"name", 1}}; + } + static constexpr std::string_view get_alias_struct_name(person*) { + return "rootnode"; + } + + YLT_REFL(person, id_, name_); void parse_xml() { struct_xml::from_xml(*this, xml_str); @@ -255,12 +262,12 @@ public: } } ``` -REFLECTION_ALIAS 中需要填写结构体的别名和字段的别名,通过别名将标签和结构体字段关联起来就可以保证正确的解析了。 +get_alias_field_names 函数中需要填写结构体的别名,get_alias_struct_name 函数中填写字段的别名,别名字段的索引需要和YLT_REFL宏定义的字段顺序一致,通过别名将标签和结构体字段关联起来就可以保证正确的解析了。 这个例子同时也展示了序列化和反序列化智能指针。 # 如何处理私有字段 -如果类里面有私有字段,在外面定义REFLECTION 宏会出错,因为无法访问私有字段,这时候把宏定义到类里面即可,但要保证宏是public的。 +如果类里面有私有字段,在外面定义YLT_REFL 宏会出错,因为无法访问私有字段,这时候把宏定义到类里面即可,但要保证宏是public的。 ```cpp class person { std::string name;