Skip to content
/ rellaf Public

c++ reflection library base on c++11 which very simple to use in order to increase productivity

License

Notifications You must be signed in to change notification settings

fankux/rellaf

Folders and files

NameName
Last commit message
Last commit date

Latest commit

d45d9b5 · May 2, 2020

History

40 Commits
May 2, 2020
May 1, 2020
May 2, 2020
May 2, 2020
May 1, 2020
Nov 22, 2018
May 2, 2020
Nov 4, 2019
May 1, 2020

Repository files navigation

Rellaf

介绍

Rellaf是一个C++反射库,得益于C++11的语法,让我们可以做到一些神奇的效果——程序在运行时可以遍历对象自身的所有成员字段名字和值,并且可以通过对象名字(字符串)获得该成员的值。 基于此,实现了一系列常用并且非常实用的功能,如:

Rellaf致力于解放C++程序员,从繁重的语法中解脱,保持高性能的同时,做到和解释型语言类似的十分爽快的体验,大大提高生产力,减少低级错误。

我们来QUICK START!

首先,我们演示一个最简单的对象,设置一个int型字段名为id

  1. 包含头文件rellaf.h,在头文件申明反射类:
class Obj : public Object {     // 继承 rellaf::Object
rellaf_model_dcl(Obj);          // 申明这个类是一个Model对象反射类
rellaf_model_def_int(id, -222); // rellaf_model_def_${type}宏用来定义类型,字段名和默认值
};
  1. 在源文件中定义反射类
rellaf_model_def(Obj);
  1. 好了,可以使用了:
Obj object;
int val = object.id();         // 初始化时返回默认值 -222

object.set_id(233);            // 修改
val = object.id();             // 字段名方法调用, 返回 233
val = object.get_int("id");    // 通过字段名字符串索引取值, 返回 233

当前支持11种基本类型:

${type} C++类型
char char
int16 int16_t
int int
int64 int64_t
uint16 uint16_t
uint32 uint32_t
uint64 uint64_t
bool bool
float float
double double
str std::string

当然,成员可以加多个。更加重要的是,支持嵌套!支持数组! 我们来看相对复杂的例子,两个基本类型,一个对象成员,一个数组。

// 子对象
class SubObject : public Object {
rellaf_model_dcl(SubObject)
rellaf_model_def_uint16(port, 18765);
};
rellaf_model_def(SubObject);

// 父对象
class Obj : public Object {
rellaf_model_dcl(Obj)
rellaf_model_def_int(id, -111);
rellaf_model_def_str(name, "aaa");

// 定义对象,字段名,类型名(必须是Model类)
rellaf_model_def_object(sub, SubObject);

// 定义数组,字段名,类型名(必须是Model类)
rellaf_model_def_list(list, Plain<int>);    // Plain是C++平凡类型Model的包装 
};
rellaf_model_def(Obj);

定义好了,对象操作

Obj object;
// 常规操作
object.set_id(123);
object.set_name("fankux");

// 初始情况下是nullptr
SubObject* ptr = object.sub();    

// 赋值一个对象, 会进行内存复制,生命周期由Model管理。
SubObject sub;
object.set_sub(&sub);

// 给对象成员的成员赋值(注意,对象成员方法返回的是指针)
object.sub()->set_port(8121);
// 通过字段名字符串索引, 若不存在则返回nullptr
ptr = object->get_object("sub");
ptr->set_port(8121);

// 取值, 返回 8121
uint16_t port = object.sub()->port();
port = object.sub()->get_uint16("port");

数组操作

Obj object;
List& list = object.list();
// 与STL风格一致的接口
size_t size = list.size();      // 返回 0
bool is_empty = list.empty();   // 返回 true

// 插入成员,会进行内存复制,生命周期由Model管理。
Plain<int> item = 222;
list.push_front(&item);
list.push_back(&item);
size = list.size();             // 返回 2
is_empty = list.empty();        // 返回 false

// 索引数组成员,注意返回的是 Model*, 转换成具体类型即可
Plain<int>* ptr = list.front<Plain<int>>();
ptr = list.back<Plain<int>>();
ptr = (Plain<int>*)list[0];
ptr = (Plain<int>*)list[1];

// 遍历
for (Model* item : list) {
    // do something
}
for (auto i = list.begin(); i != list.end(); ++i) {
    // do something
}

// 修改
Plain<int> item_to_mod = 222;
list.set(0, &item_to_mod);
list.set(1, &item_to_mod);

// 弹出
list.pop_front();
list.pop_back();

// 清空
list.clear();

详细使用见 API文档

C/C++枚举(enum)能力很有限,只是一个数字,没有从字符串获得枚举的能力,也不能判断一个枚举是否存在。
Rellaf实现了灵活的枚举类,使用同样非常简单。

  1. 包含头文件"enum.h",申明枚举类
class DemoEnum : public Enum {
rellaf_enum_dcl(DemoEnum);

//  按照 code(int),name(std::string) 定义,都保证唯一。
rellaf_enum_item_def(0, A);
rellaf_enum_item_def(1, B);
rellaf_enum_item_def(2, C);
};
  1. 源文件定义枚举类
rellaf_enum_def(DemoEnum);
  1. 可以使用了, 枚举类的成员类型都是rellaf::EnumItem
// 枚举都是单例类,通过单例方法或者 rellaf_enum宏 访问
std::string name = DemoEnum::e().A.name;                    // 返回 "A"
int code = rellaf_enum(DemoEnum).B.code;                    // 返回 1

// 静态数字,可用于switch case (仅C++17以上)
code = DemoEnum::A_code;                                    // 返回 1
code = DemoEnum::B_code;                                    // 返回 2
code = DemoEnum::C_code;                                    // 返回 3

// 比较
if (DemoEnum::e().B != DemoEnum::e().C) {
    // B not equal C
}

// 判断是否存在
DemoEnum::e().exist(2);                                     // 返回 true
DemoEnum::e().exist("D");                                   // 返回 false

// 取值
EnumItem name = DemoEnum::e().get("B");
if (name.available()) {
    // exist, do something
}
EnumItem code  = DemoEnum::e().get(1);
if (code.available()) {
    // exist, do something
}

// 获得取值范围
const std::map<std::string, int>& names = DemoEnum::e().names();
const std::map<int, std::string>& codes = DemoEnum::e().codes();

实现Rellaf对象Json相互转换。详细使用见 API文档

这是一个Java Mybatis like的SQL语句生成器。并不是说使用方式和语法与Mybatis一样,我们强调写代码体验,Rellaf做到的是在写Dao的体验上,尽可能靠近Mybatis,简单灵活而'自动化'。用户简单配置一个SQL模板,然后Rellaf将生成Dao执行方法,运行过程中'自动'将Model填入SQL模板,生成可执行SQL语句,执行SQL(需要实现Mysql数据传递接口)后,将返回值'自动'转换为Model对象返回用户。

src/mysql
包含一个简单的Mysql连接池实现,这个模块为了对接SQL生成后的执行过程。

详细使用见 API文档

brpc是baidu内部的rpc组件,真正意义上终结了公司内部网络传输组件的混乱之治,统一RPC场景。内部叫baidu-rpc,第一次接触大概是在2016年,那会儿还没开源,项目需要做HTTP服务端,“看上去可用”基本只有这一款,使用后,惊为天人,接入简单,protobuf直接定义接口,一个端口,支持多种协议,性能极高,自带的bthread还可以扩展到非RPC的通用并发场景。几年下来,稳定可靠,体验极佳。 不过HTTP这块,封装还是比较初步的,可以先看一下官方文档 ,大体上,只是对协议层面的封装,而没有考虑业务层面(并不是说用C++写业务,但很多情况下总是要实现HTTP服务),对于HTTP常用使用模式(套路)的封装涉及不多,比如:

  • 把所有接口和proto接口签名拼接在一个字符串中,proto接口签名生成service请求入口后,不能够直观的看到HTTP API(请求路径)与 service请求入口之间的关联,API多了后会很混乱。
  • BRPC的HTTP接口不使用protobuf service接口函数的request,response字段,但是每个请求入口必须写出来,不够简洁。
  • 不具有HTTP Json接口的套路(body解析为Json)。
  • path variable支持很有限。

这么几个问题,都是长期使用过程中总结的,由于BRPC定位并不是Spring那样“包办一切”,我自己也持续开发出了一套封装方法,并用到了线上,尽可能做到“简单可依赖”。于是,我把这个部分提取出来,作为rellaf的一个扩展。

详细使用见 API文档

编译

不开启扩展的话,不依赖第三方组件。开启不同扩展依赖不同第三方库。
编译选项:

选项 默认 说明 依赖
WITH_JSON ON json扩展 jsoncpp
WITH_MYSQL ON 简单mysql连接池 mysqlclient
WITH_BRPC_EXT ON brpc接口映射 brpc
WITH_TEST ON 单元测试 gtest

安装依赖(可选):
ubuntu/WSL

sudo apt-get install libssl-dev libjsoncpp-dev libmysqlclient-dev libgtest-dev 
# 注意,libgtest-dev这个源安装是源码,需要进入目录/usr/src/gtest(也可能是/usr/src/googletest/googletest)
# 执行 sudo mkdir build && cd build && sudo cmake .. && sudo make && sudo make install

# 开启brpc还需要额外安装以下依赖:
sudo apt-get install libssl-dev libgflags-dev libprotobuf-dev libprotoc-dev protobuf-compiler libleveldb-dev libgoogle-perftools-dev

centos

sudo yum install libjsoncpp-devel libmysqlclient-devel gtest-devel 

macOS

brew install jsoncpp mysql-connector-c
# 注意,gtest需要从源码安装,见 https://github.com/google/googletest

最小化编译:

mkdir build && cd build
cmake —DWITH_JSON=OFF -DWITH_MYSQL=OFF -DWITH_BRPC_EXT=OFF -DWITH_TEST=OFF .. && make

意义何在?
很多典型场景下,如现今服务端程序两大"刚需":Json序列化和拼SQL。写过Java的同学可能不以为然,写一个和Json对象成员对应的Model类,Gson,Jackson双向"一键直达";拼SQL?Mybatis的SQL模板中条件预留好字段名称,例如WHERE field=#{成员名},调用时传递对象,同样"一键直达",Mybatis能够自动根据名称拿到对象的成员值。

以上两个场景都需要反射。

传统C++怎么做?(摘自真实线上代码,已"打码")

json["name"] = _field_name;
///////////////////
_count_big = threshold.get("count_big", 10000).asInt();

同样的字段名我们需要写两遍,变量写错了还好,编译不过;Json取值的索引字符串错了只能运行时等报错。

std::string sql = "INSERT INTO XXX_OOO(product, stream, profile, environment, "
        "plat_product, uniq_stream, plat_profile) VALUES('" + iden.product + "', '" +
        iden.stream + "', '" + iden.profile + "', '" + iden.environment + "', '" + pdb +
        "', '" + uniq_stream + "', '" + platform + "') ON DUPLICATE KEY UPDATE plat_product='" +
        pdb + "', uniq_stream='" + uniq_stream + "', plat_profile='" + platform + "'";

如果说上面处理Json还能接受,这个简直是折磨,不言而喻……你可以用sprintf这类,但是%xxoo和后面变量检查对应同样很痛苦,没有本质区别。

如何实现?
C++11特性,成员变量就地初始化,可变参模板。静态注册套路。另外,宏, 大量的宏, 对, 多到令人发指的宏。虽然,宏在课堂里或者大部分编程规范里,是badcase,然而我们不得不这么做,至少C++11的语法范围,不得不用宏。
TODO 实现细节

为什么C++11?
因为公司早已全面推广GCC4.8.2,C++11完全支持,14,17甚至20,虽然能更加轻易实现,但兼容性还不敢想。国内大部分厂估计也是这种情况。

'Rellaf'单词什么意思?
relief(得到解脱),reflection(反射),relax(放松),3个单词组合一下,得到'Rellaf'。

最后

本人能力有限,精力也有限,存在不足,欢迎交流指出。 [email protected]

About

c++ reflection library base on c++11 which very simple to use in order to increase productivity

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published