在变参展开的同时,展开另外一个变参

展开过程是先展开里面的变参,再展开外面的变参。
代码来自:http://arne-mertz.de/2016/11/more-variadic-templates/

如何设计一个简单的C++ ORM

如何设计一个简单的C++ ORM

注:本文是由社区的BOT Man写的,由我代为发上来。

2016/11/15

“没有好的接口,用C++读写数据库和写图形界面一样痛苦”

阅读这篇文章前,你最好知道什么是
Object Relation Mapping (ORM)

为什么C++要ORM

As good object-oriented developers got tired of this repetitive work,
their typical tendency towards enlightened laziness started
to manifest itself in the creation of tools to help automate
the process.

When working with relational databases,
the culmination of such efforts were object/relational mapping tools.

  • 一般的C++数据库接口,都需要手动生成SQL语句;
  • 手动生成的查询字符串,常常会因为模型改动而失效 (这里超级容易出错);
  • 查询语句/结果和C++原生数据之间的转换,每次都要手动解析;

市场上的C++ ORM

ODB

需要一个独立的 预编译器 生成模型和操作;

sqlpp11

使用 生成模型和操作,使用起来也比较复杂;
不过个人比较喜欢这个设计;

Hiberlite ORM

  • 需要在定义模型时,插入 额外的代码 进行注入;
  • 没有条件查询
    Transaction

C++的ORM要做什么

  • 将对C++对象操作转化成SQL查询语句
    (LINQ to SQL);
  • 提供C++ Style接口,更方便的使用;

一个可行的设计

一个实现了基本功能的C++ ORM —— ORM Lite

关于这个设计的代码和样例:

https://github.com/BOT-Man-JL/ORM-Lite

设计上大致分为6个方面:

  1. 封装SQL链接器
  2. 遍历对象内需要持久化的成员
  3. 序列化和反序列化
  4. 获取类名和各个字段的字符串
  5. 获取字段类型
  6. 将对C++对象的操作转化为SQL语句

1. 封装SQL链接器

为了让ORM支持各种数据库,
我们应该把对数据库的操作抽象为一个统一Execute

  • 因为SQLite比较简单,目前只实现了SQLite的版本;
  • MySql版本应该会在
    这里 维护。。。

2. 遍历对象内需要持久化的成员

2.1 使用 Visitor Pattern + Variadic Template 遍历

一开始,我想到的是使用
Visitor Pattern
组合
Variadic Template
进行成员的遍历;

首先,在模型处加入 __Accept 操作;
通过 VISITOR 接受不同的 Visitor 来实现特定功能;
并用 __VA_ARGS__ 传入需要持久化的成员列表

然后,针对不同功能,实现不同的 Visitor
再通过统一的 Visit 接口,接受模型变长数据成员参数;
例如 ReaderVisitor

  • Visit 将操作转发给带有变长模板_Visit
  • 变长模板_Visit 将各个操作转发给处理单个数据_Visit
  • 处理单个数据_Visit模型的数据
    Visitor 一个 public 数据成员( serializedValues)交换;

不过,这么设计有一定的缺点:

  • 我们需要预先定义所有的 Visitor,灵活性不够强;
  • 我们需要把和需要持久化的成员交换的数据保存到 Visitor 内部,
    增大了代码的耦合;

2.2 带有 泛型函数参数 的 Visitor

(使用了C++14的特性)

所以,我们可以让 Visit 接受一个泛型函数参数,用这个函数进行实际的操作;

模型处加入的 __Accept 操作改为:

  • fn泛型函数参数
  • 每次调用 __Accept 的时候,把 fn 传给 visitorVisit 函数;

然后,我们可以定义一个统一的 Visitor,遍历传入的参数,并调用 fn ——
相当于将 Visitor 抽象为一个 for each 操作:

最后,实际的数据交换操作通过传入特定的 fn 实现:

  • 对比上边,这个方法实际上是在处理单个数据_Visit模型的数据
    传给回调函数 fn
  • fn 使用
    Generic Lambda
    接受不同类型的数据成员,然后再转发给其他函数( DeserializeValue);
  • 通过capture需要持久化的成员交换的数据;

2.3 另一种设计——用 tuple + Refrence 遍历

(使用了C++14的特性)

虽然最后版本没有使用这个设计,不过作为一个不错的思路,我还是记下来了;

首先,在模型处通过加入生成 tuple 的函数:

  • forward_as_tuple__VA_ARGS__ 传入的参数转化为引用的 tuple
  • decltype (auto) 自动推导返回值类型;

然后,定义一个 TupleVisitor

  • 其中使用了 _SizeT 巧妙的进行 tuple 下标的判断;
  • 具体参考

    http://stackoverflow.com/questions/18155533/how-to-iterate-through-stdtuple

最后,类似上边,实际的数据交换操作通过 TupleVisitor 完成:

2.4 问题

  • 使用 Variadic Templatetuple 遍历数据,
    其函数调用的确定,都是编译时就生成的,这会带来一定的代码空间开销;
  • 后两个方法可能在 实例化Generic Lambda 的时候,
    针对 不同类型的模型的 不同数据成员类型 实例化出不同的副本,
    代码大小更大;

3. 序列化和反序列化

通过 序列化
将 C++ 数据类型转化为字符串,用于查询;
通过 反序列化,
将查询得到的字符串,转回 C++ 的数据类型;

3.1 重载函数 _Visit

针对每种支持的数据类型重载一个 _Visit 函数,
然后对其进行相应的序列化和反序列化

以序列化为例:

3.2 使用 std::iostream

然而,针对每种支持的数据类型重载,这种事情在标准库里已经有人做好了;
所以,我们可以改用了 std::iostream 进行序列化和反序列化

以反序列化为例:

4. 获取类名和各个字段的字符串

我们可以使用中的 # 获取传入参数的文字量;
然后将这个字符串作为 private 成员存入这个类中:

其中
#_MY_CLASS_类名
#__VA_ARGS__ 为传入可变参数的字符串;
__FieldNames 可以通过简单的字符串处理获得各个字段的字符串

5. 获取字段类型

新建数据库的 Table 的时候,
我们不仅需要类名和各个字段的字符串,
还需要获得各个字段的数据类型

5.1 使用 typeid 运行时判断

Visitor 遍历成员时,将每个成员的 typeid 保存起来:

运行时根据 typeid 判断类型并匹配字符串:

5.2 使用 <type_traits> 编译时判断

由于对象的类型在编译时已经可以确定,
所以我们可以直接使用 <type_traits> 进行编译时判断:

我们可以使用一个 Query 对象,专门处理条件查询;
并在其带有条件的操作中,返回自己的引用
从而实现Fluent Interface

6.2 自动将C++表达式转为SQL表达式

首先,引入一个 Expr 类,用于保存条件表达式;
Expr 对象传入 ORQuery.Where,以实现条件查询:

  • expr 保存了表达式序列,包括该成员的指针关系运算符 值的字符串;
  • 重载 && || 实现表达式的复合条件

不过这样的接口还不够友好;
因为如果我们要生成一个 Expr 则需要手动传入 const std::string &relOp

所以,我们在这里引入一个 Field_Expr 实现自动构造表达式

  • Field 函数用更短的语句,返回一个 Field_Expr 对象;
  • 重载 == != > < >= <= 生成带有对应关系的 Expr 对象;

6.3 自动判断C++对象的成员字段名

由于没有想到很好的办法,所以目前使用了指针进行运行时判断:

相当于使用 Visitor 遍历这个对象,找到对应成员的序号

6.4 问题

之后的版本可能考虑:

  • 支持更多的SQL操作,并改用语法树实现;
  • 提供Transaction;(欢迎 Pull Request)

写在最后

这篇文章是我的第一篇技术类博客,写的比较浅,见谅;

你有一个苹果,我有一个苹果,我们彼此交换,每人还是一个苹果;
你有一种思想,我有一种思想,我们彼此交换,每人可拥有两种思想。

如果对以上内容及ORM Lite有什么问题,
欢迎 指点 讨论

https://github.com/BOT-Man-JL/ORM-Lite/issues

Delivered under MIT License © 2016, BOT Man

Trump和C++

在美国大选尘埃落定之际,我想聊聊Trump和C++,很多人也许会觉得奇怪,Trump这个新当选的美国总统和C++有啥关系吗,难道他是一位C++程序员?!其实此川普非彼川普,原来是一位叫Trump的C++程序员,他是Trump的粉丝,让我们来八卦一下这位C++界的川普吧。这是他的twitter
qq%e5%9b%be%e7%89%8720161109214839

他的图像就是新当选的美国总统Trump,说明他是支持Trump的粉丝。没想到之前一直不被看好的川普逆袭成功,本来想看看他的twitter上有没有相关的段子,结果没看到。从他的twitter看到,他还参加了今年的cppcon,他的演讲主题我还没查到,应该比较精彩。

qq%e5%9b%be%e7%89%8720161109214905

我之前并不知道C++界有一位Trump,是C++标准委员会的Mike Spertus告诉我的。这次参加C++及系统软件技术大会,晚上大家一起吃饭的时候,我坐在Andrei Alexandrescu和Mike Spertus旁边,和他们闲聊,聊到美国大选和川普。Mike说C++ Trump,我还挺吃惊,以为美国总统候选人和C++有什么渊源,后来听他解释才知道是这么回事。再聊聊美国大选,我问Andrei和Mike他们支持希拉里还是川普,Mike说Trump is crazy,还说希拉里的支持率远远超过Trump,认为Trump的机会不大。Andrei也赞同这个观点,看得出他们挺支持希拉里当选美国总统的,也挺有信心的。同桌的小伙伴问,如果Trump当选美国总统了,他们会怎么办。Mike说如果Trump真当选了,他只有接受现实了。没想到一语成谶,川普逆袭成功^_^。
qq%e5%9b%be%e7%89%8720161109214927

Mike还问我如果Trump当选了会对中国产生什么影响。我说川普当选会对中国等国家更友好。Andrei和Mike酒量不错,每人大概喝了五瓶啤酒,还说青岛啤酒在美国也很知名。
再分享一下C++之父在大会上的风采
qq%e5%9b%be%e7%89%8720161109215211

能聆听这位带领我们cpper前进的精神领袖的演讲,真的很激动,我坚信C++会越来越好,C++程序员的日子也会越来越美好!
希望明年能有机会在cppcon上和那位C++ Trump见面聊聊^_^

C++14实现c++17的make_from_tuple

//test code

rest_rpc v0.91 release

新增特性

1.业务函数的参数可以有connection_ptr,也可以没有,取决于你的需要,使用更灵活。

2.客户端添加private接口,拥有更高的权限和更多的流程控制

3.server端的pub提供了一个纯转发的重载实现

4.提供管理多个endpoint的工具

5.客户端pub接口的,将会把转发协议的name当做topic,广播给所有监听这个topic的客户端,而不需要再服务器上注册handler;

6.服务器注册handler,将使用hash值代替字符串

Bug修复

1.rpc超时后异步调用链断开

2.客户端和服务器read大块消息时,因使用boost::bind,而发生了意外地拷贝,招致读取到不正确的地址

3.支持更低版本的编译器

详细可以看这里。

Copy Protected by Chetan's WP-Copyprotect.