Skip to content

Commit

Permalink
update reflection doc (#783)
Browse files Browse the repository at this point in the history
  • Loading branch information
qicosmos authored Sep 25, 2024
1 parent 4ccad48 commit 6980632
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 6 deletions.
4 changes: 4 additions & 0 deletions website/.vitepress/config/zh_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
1 change: 1 addition & 0 deletions website/.vitepress/config/zh_locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const zh_themeConfig = <DefaultTheme.Config>
{ 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 },
]
};

Expand Down
6 changes: 5 additions & 1 deletion website/docs/zh/guide/what_is_yalantinglibs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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++应用。

Expand Down Expand Up @@ -434,6 +434,10 @@ yalantinglibs工程自身支持如下配置项,如果你使用cmake find_packa

无依赖。

### reflection

无依赖

## 独立子仓库

coro_http 由独立子仓库实现: [cinatra](https://github.com/qicosmos/cinatra)
Expand Down
2 changes: 2 additions & 0 deletions website/docs/zh/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file added website/docs/zh/reflection/reflection.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
236 changes: 236 additions & 0 deletions website/docs/zh/reflection/reflection_introduction.md
Original file line number Diff line number Diff line change
@@ -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<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:
```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<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等格式。

![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<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了,如果对象的字段都是私有的,则需要将宏定义到对象内部:
```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。
17 changes: 12 additions & 5 deletions website/docs/zh/struct_xxx/struct_xxx_introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ include_directories(include/ylt/thirdparty)
gcc9+、clang11+、msvc2019+

# json 序列化/反序列化
序列化需要先定义一个可反射的对象,通过REFLECTION 可以轻松定义一个可反射对象。
序列化需要先定义一个可反射的对象,通过YLT_REFL 可以轻松定义一个可反射对象。

```cpp
struct person
Expand All @@ -33,7 +33,7 @@ struct person
};
YLT_REFL(person, name, age); // 通过这个宏定义元数据让person 成为一个可反射对象
```
通过REFLECTION 宏定义元数据之后就可以一行代码实现json 的序列化与反序列化了。
通过YLT_REFL 宏定义元数据之后就可以一行代码实现json 的序列化与反序列化了。
```cpp
person p = { "tom", 28 };
Expand Down Expand Up @@ -245,7 +245,14 @@ public:
person() = default;
person(std::shared_ptr<std::string> id, std::unique_ptr<std::string> 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);
Expand All @@ -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;
Expand Down

0 comments on commit 6980632

Please sign in to comment.