C++ 杂谈(16)

前段时间 cppcon 落下帷幕,youtube 上新的一期视频也逐渐发布出来,估计又要好好的学习一段时间了。其实看那些东西还是很有收获的,最近这两年 C++ 真的是大跃进啊大跃进,憋了那么多年的标准从 98 到 11 花了 13 年,而似乎后面不断的新标准新 idea 开始涌入这个沉寂了多年的语言,一转眼我的认知还停留在 gcc 4.9,再一看 gcc 5 已经出来很久了,默认使用 gnu++11 而更令我吃惊的是其实 gcc 6 的计划也出来了,用的已然是 gnu++14 了,居然可以用 gnu++1z 使用 concept 了 Orz,那么我们赶紧的…

expression template

这个概念其实说了很久了,这次听了 Joel 同志的 talk 理解更加清晰了。从某个角度来说 expression template 的目的是将一个 expression 转换成为一个 type,利用 type 来表达一个操作或者过程,往往一个 expression 在 compiler 的世界里面都会使用所谓的 AST(abstract syntax tree)来进行表达(parser 的角色就是将一种 language 转换到 AST,供后面的工作使用)。在 C++11 的 auto 关键字引入之后,expression template 甚至可以更加容易的处理如下表达

auto d = a + b + c;

之前想要表达 d 其实是一个 expression,对应的 type 可能是一个异常复杂的 <plus_, <plus_, terminal<int>, terminal<int>>, teriminal<int>>,估计如果要继续 compose 几下,没有人有耐心把这个 type 给定义出来(当然可以用 meta programming 算出来,但是其实这个 type 对人的理解来说完全没有任何额外的帮助)。现在 auto 万岁了。

另外 terminal 和 AST 之间的赋值成为了一种类似 flush buffer 的操作,因为本质上可以在这种赋值的时候将一个复杂的 AST 处理成为“运算”之后得到的结果,这里之所以说“运算”是因为不同的问题下运算的含义是不大一样的:

  • 很多矩阵运算的 library,如 boost.numeric.ublas 或者 Eigen 都会使用这个技术将很多操作 bind 到一个更加 efficient 的实现上(如调用 BLAS,或者避免产生临时变量)
  • 考虑一个 RE 的实现可以考虑将 AST 转换到 automata 的实现
  • 考虑一个类似 FlumeJava 的分布式计算 library,这个过程可能只是记录一下某个抽象出来的数据是如何通过运算生成的

有了这层理解,我们回过头来看 boost.proto 就会有了更为清晰的认识。正好公司里面的 project 用了一个列存储格式上类似 SQL 的东西,一下子把运行效率大幅提高(>50% runtime reduction),只是那玩意的 client 感觉非常原始,没有数据库上的 ORM,也没有之前玩 jOOQ 的 EDSL。最后的结果可想而知,字符串的 manipulation,本来还可以比较 clean 的 code 最后因为复杂的 templatization 导致 code 难读得要死。于是网上搜了一下,还真有一个用 expression template 实现的 sqlpp11,不过公司里面肯定不会让用(当年 Amazon 里面也好还是 Yahoo! 也好,这个宽松多了),主要是公司一直坚持用一个自己的 string 而不用 std::string,还有很多别的事情(比如不支持 exception)。奇怪的是并不是没人想用这个东西,因为很明显本地就有个人在为 Java 写个类似的 client,说是写完之后再 port 到 C++。

当然,这个想法还有更深一种方式的应用。之前的想法基本上是理解 SQL 的 grammar,然后用 expression template 定义一个 EDSL,用 SQL 的 grammar 做 compile-time checking,然后 evaluation 的过程就是将 AST 转换到 string 的过程。这个好处大约是只要几个 backend engine 支持一样的 SQL 语言,就可以将这个 library 应用到不同的 backend 上去(比如不同的 DB,事实上公司里面除了列存储的 query engine 还有对外公开过的 F1 等等),每个 evaluation context 维护一个对应 client 的 instance 就 okay 了。

更深层一点的想法是,SQL 本身也需要被 parse 形成最终的 execution plan 最后通过 worker 去实现,是否我们可以将 expression template 获得的 AST 直接映射到 backend parsing 之后的 AST 呢?这样的映射可以省去 convert 到 string 这样一步 type erasure 的行为:

  • C++ 类型到 string,我们需要将具有 type 的信息,如 string、int 完全转换到一个 string,这时我们甚至都不能知晓转换之后时候会不会带来 runtime error(该传 int 结果写了个 string,最后要 backend 说这个 query 失败)
  • string 到 C++,在 backend 我们需要将没有 type 信息的 string parse 到 AST 然后做 validation,最后去执行

这样一个过程最大的漏洞莫过于 SQL injection,我们需要对很多传递的 string 进行 validation 或者 escaping 确保进入 type erasure 状态以前没有 injection 的发生。

那么很显然 AST 到 AST 的转换两侧都是有 type 信息的,这类问题就能最大程度的被消除,甚至变成 compile-time error。当然坏处也很明显,这个 EDSL 与不同的 backend binding 将会产生不同的 compile-time 的行为,实现起来也会更加的 challenging。

小工具

boost/core/demangle.hpp 是个很不错的东西,之前看见过 gcc 的一个 API,这个支持的估计就不仅仅是 gcc 了,在面对 expression template 生成的复杂 type 的时候,这个玩意绝对的是非常 handy。

——————
The keeper of the prison looked not to any thing that was under his hand; because the Lord was with him, and that which he did, the Lord made it to prosper

Advertisements
C++ 杂谈(16)

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s