Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[doc]update reflection doc #783

Merged
merged 1 commit into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading