知行一 | 顶级C++社区

寻找一名懂css尤其是bootsrap的志愿者

现在purecpp社区正在从php移植到c++ web开发框架feather,目前数据迁移工作已经完成,现在主要问题是css布局和格式的问题,由于我对css不熟,解决这些问题很慢,需要志愿者帮助解决一些css显示问题,加快社区的移植工作。

有意向着请联系 purecpp@163.com
或者在下面留言留下你的联系方式。

cppcon2018日程确定

随着cppcon2018的时间越来越近,今年的cppcon日程终于确定了,一百多位来自世界各地的讲师将给大家带来精彩的演讲,c++爱好者不可错过。大会详细日程在这里,大家可以根据自己的兴趣选择自己感兴趣的话题。

purecpp社区会继续关注并带来关于今年cppcon的最新资讯。

如果你没有机会去美国参加cppcon,你可以报名参加年底中国的cppcon,我们期待着您分享创新的idea。

cinatra发布新版本

cinatra本次更新主要侧重于简化接口使用,增加一些功能,修复一下bug。这仍然是一个预发布版本,但强烈推荐大家试用。

这次的版本具体有这些更新:

1.简化文件上传接口的使用,使用示例:

非常简洁,用户直接获取上传的文件即可,框架负责处理文件上传的细节。

2.简化文件下载功能

框架内置chunked文件下载功能,只要输入文件路径就可以实现下载:

http://127.0.0.1:8080/assets/show.jpg

更棒的是文件下载支持断点续传,这对大文件下载很方便。

3.简化websocket接口的使用

用户只需要在各自的事件响应函数里写逻辑即可,非常方便。

4.提供了一些更便利的render html接口

5.修复了一些bug

  1. 修复了和nginx结合的时候cookie的一个bug
  2. 修复了multipart同时有文件和键值对时忽略了键值对的bug;
  3. 修复了multipart传多个文件时丢失header的bug;
  4. 修复了url请求不支持中文的bug;

欢迎大家试用并提出宝贵的建议和意见,让cinatra越来越完善.

Hope you can enjoy cinatra!

记一次C++静态反射的实践

1. OBJECTIVE

实现目标,能够获取一个用户自定义类型的成员变量和成员方法,包含静态和非静态的。实现方式有两种,侵入式和非侵入式。侵入式的方案,可以比较好的处理保护类型和私有类型的成员,但是面对无法修改源码的类库却束手无策。非侵入式的实现不需要修改用户自定义类型,可以很好的处理外部的类库,但是又无法处理对非公有成员。两种方案各有优劣,笔者个人是倾向于非侵入式的方案。毕竟,即使反射出了非公有的成员,我们也无法访问封装好的非公有成员。

量化具体的目标,详细如下:
1. 访问类名;
2. 访问公有的非静态成员变量;
3. 访问公有的非静态成员函数;
4. 访问公有的静态成员变量;
5. 访问公有的静态成员函数;
6. 访问基类;

用代码接口来更详细的表达具体目标:

  • 类名:命名空间::类名,这里就是foo或者::foo;
  • 非静态成员变量:使用pointer to memeber data(PMD)的元组,这里是

  • 非静态成员函数:使用pointer to member function(PMF)的元组,这里是:

  • 静态成员变量:使用指针类型的元组即可,使用时同样要注意一处定义原则(ODR):

  • 静态成员函数:使用函数指针的元组即可:

  • 基类:导出所有基类的类型,单继承导出一个类型,多继承导出基类的元组。

2. MANUAL VERSION

为了降低实现的难度,笔者在尝试的时候是先写出手动实现的版本。最终,这些手写的代码会使用宏来替代。最终宏需要生成的版本,大概是这样的:

上面的代码是反射库提供的基础外观,针对需要反射的类型,就要生成对应的特化代码。

3. SEMI-AUTOMAIC

手写这些代码很啰嗦也很不高效,但是宏可以缩减这些代码。我自己用宏实现的半自动版本,虽然看起来也很啰嗦,如下:

在实践的过程中,我碰到的最大的挑战就是构造函数和函数重载。构造对象是一种语义,所以构造函数是无法获取地址的,也可以认为构造函数不是函数,详细可以参考Inside C++ object model中的The Semantics of Constructors一章。其次就是函数重载,因为对重载函数取地址,编译期会抱怨不知道取的是哪一个?

构造函数的解决方案,我用了一个元组保存构造函数的参数列表,然后再用一个元组记录所有的构造构造函数。

有一个细节需要注意,对于非默认构造函数,需要检查参数的类型,不能有void. 如果有需要用static_assert报编译期错误。接下来就是函数重载了,如果给foo增加一个成员函数get的重载版本:

就需要显示的把每个get的signature都写出来:

4. IMPLEMETATION DETAILS

实现的时候,我使用了boost.pp库。宏元编程是一个很烧脑的事情,不过有了boost.pp情况会好很多,但依然很烧脑。介绍思路以前,先简介一下boost.pp的抽象的三种基于宏token的数据结构:sequence, tuple和list.

这里需要注意的是,PP使用逗号”,”作为tokens的划分,所以一定要处理好C++ templates.

我在实现中使用了宏元的tuple和sequence. IGUANA_REFLECT(…)
展开后,实际上是如下形式:

简单解析一下,IGUANA_REFLECT宏的输入只有两个tokens。第一个token就是foo类,除了生成类名,稍后会用作sequence遍历算法的上下文。而第二个token是一个sequence,每个sequence内部是一个tuple.

每个tuple都是一个二元的元组,展开的结果如下:

tuple的第二个token依旧是一个tuple,例如((), (int))和(set, get). 最外层的IGUANA_REFLEECT实际上使用了sequence的for_each算法,对sequence的每个元素进行展开:

展开之后的效果大概是这样的:

每个IGUANA_XXX_PROCESS宏,负责实际生成获取成员名和获取成员指针的代码。这一部分的代码是基于iguana之前的实现改进的,这里就不赘述了,详细的代码可以参照此链接。我暂时还没有上传示例代码,后面会添加上,更新在文章中。

5. TRAVERSE

反射代码是可以半自动生成了,但是使用起来不是很方便。所以我试着提供了一个visitor的机制,可以比较方便的访问:

虽然我使用了一些模板元,visitor可以选择性的遍历各种成员,还是挺啰嗦了。如果大家有什么好的idea,欢迎交流。

6. LIMITATION & SUMMARY

此次尝试死了不少脑细胞,结果还是有一些限制:
1. 不能反射私有成员;
2. 不能反射函数模板;
3. 还是很啰嗦;
这种用宏的半自动方案,其实可以利用libclang,在pre-compile期做一次分析,然后生成这一份半自动代码,从而改进到全自动的目的。

最后,C++反射依旧需要标准的完善才能有一个比较完备的结果。反射标准就目前的进度来看,最早得C++20才能进TS,等到所有的编译器完整支持,可能到C++23才能够使用。所以,撸一个符合自己项目需求的反射方案,至少还能够服役五年。

以上是我自己实践C++编译期反射的一些思路,肯定有不少槽点,欢迎来purecpp社区吐槽和交流。QQ群号:296561497.

公司招聘信息(持续更新)

招聘信息1:

公司:
国内量化对冲领域 top 3 公司
该公司是一家依靠数学与人工智能进行量化投资的对冲基金公司,当前管理规模逾50
亿。自2008 年起致力于量化对冲的研究、创新与实践。依靠强大的系统、独特的模型、严谨的风控,结合宏观与基本面研究,多年来始终保持令人瞩目的优秀投资业绩,为客户稳定创造价值,被评为最受机构尊重的私募基金之一。
出色的业绩来源于出色的团队,该公司的投资和技术团队有80%以上毕业于国内排名前5 和世界著名
的高校,如加州伯克利、卡耐基梅隆、牛津、伦敦政经、新加坡国立等,其中更是包括了国内最早的量化交易者、金牛奖获得者、智能机器人科研领域、互联网大数据与模式识别的专家。

岗位职责:
1.建立自动化交易和量化研究的基础架构,支持百亿级别的资管产品。
2.不断追求更高的性能,可靠性,易用性 ,可维护性。
3.与研究人员一同探索策略研究和实现的最佳方案。

任职资格:
1.熟练掌握C++和至少一种其它编程语言。C++是高性能计算的首选;同时我们相信问题的解决方案不
止一种,需要灵活的选择合适的技术来构建系统。
2.熟悉敏捷开发的流程。我们使用看板、代码评审、自动化测试、持续发布等工具来提升效率。
3.具有自我驱动和自我管理能力,能够针对系统的不足提出改进方案并推动实现。我们鼓励自下而上推动
进步的模式。
4.希望你是某个领域的专家,能以独特经验和独到观点,开拓架构视野,从不同维度优化系统。
5.具有良好的团队协作与沟通能力,认同开放共进的企业文化。

核心要求:
学校门槛:本科为985 高校,最好是清北复交,浙大,南大,中科大,北航,西交,哈工大,电子科技大学这几所。或国外CS 优秀高校。
加分项:在国际优秀互联网公司工作过,google,微软,facebook,亚马逊等,其次是国内的一线互联网公司。

看重点:
学校好,综合素质和潜力好,喜欢开发且编程功底扎实,爱钻研,能独挡一面,可以在未来成为优秀的开发团队管理者。

欢迎大家发简历到 willdeng@michaelpage.com.cn 我将及时与您联系。

从抽象谈面向对象与泛型编程 part.2 – 泛型编程的抽象

1. 前言

上一篇浅讨了OOP中抽象常用的方法,而这一篇的内容,主要是对《数学与泛型编程》,后文简称MGP,中部分内容的小结。这本著作有相当多的篇幅,是对数论基础的讨论。其中的一条主线,就是最大公约数(GCD)算法的探讨。GCD是数论的基础,可以说绝大部分数论的定理都是基于GCD推导出来的,而整个数论的发展史就是一部推广GCD算法的历史。GCD算法推广的过程,也演绎出了很多数学中常用的抽象方法。而GP的抽象,从一个具体的算法或者业务出发,抽象出一个适用于一个类型集合的外观,这个过程与数学定理的推广过程很类似。所以这一篇,我们将以最大公约数算法的推广为主线,来探讨泛型编程中的抽象方法。

2. 最大公约数算法

GCD早在古希腊,毕达哥斯拉学派中,就有了很深的研究。古希腊时的人们还没有抽象出整数这个数学工具,甚至连零的概念都没有。那个时候人类研究的都是具体的问题,例如使用单位长度的绳子进行测量。GCD就是其中一个很经典的问题,求出两条线段的公尺量。下面的代码,实现的是最经典的GCD算法(这里假设a的长度大于b,即使是b大于a,我们也可以通过交换位置得到相同的条件,因此后面的GCD版本都假设a大于b

GCD的原始版本可以求出两条线段的公尺量,就是求出一条能够同时测量两条线段的单位线段。公尺量作为单位1,建立起了一个类似正整数的模型,因此零没有引入到系统中来。对于非负整数集合而言,零是必须要考虑的情况,下面的代码就是计算两个32位无符号整形的GCD代码:

这里有一个不严谨的地方,就是无符号整形无法映射到整个非负整数的集合,因为前者是一个有限集合。但是对于gcd算法而言,是不会出现算法溢出的,原因是说给定GCD的两个入参a和b,是不会计算出比a和b更大值的情况。因此我们可以在这里忽略计算机无符号整形不能与非负整数同构。

回到正题,算法很简单,这是一个辗转求余的过程。数论的研究是要把它推广到适应更广泛的对象,而对于程序的实现而言,就是要推广它能够适应更多的类型。添加一个支持64位整数的版本,利用函数重载就很容易实现:

3. 支持有符号的整数

当我们把GCD从非负整数推广到整个整数集合的时候,其实就是确定如何对负整数求余的操作的。如果把全体整数列在一条直线上,把每个整数i看作是一个从0出发,以1位单位,长度为i的有向线段。那么负整数的绝对值,就是它的长度了。我们把GCD推广到整数的方法,就是把对数求余变成对数的度量进行求余。

当然,我们目前所使用的计算机体系,是可以使用对负数取模操作来计算余数的,实际上我们可以一行代码都不需要修改。

4. 支持更多的整数类型

之前版本的GCD就可以处理所有全部的整形了,但它也只能处理整形。原因就是我们使用了取模操作来计算余数,这个只能对整形数据求余,但对其他整数类型的数据无效,例如高斯整数。

高斯整数是形如x = m + in的整数,它有实部和虚部两个部分。我们可以使用标准库已经实现的complex来表示高斯整数:

推广GCD到高斯整数,我们就要实现高斯整数的带余除法的求余部分:

有了求余的函数,就可以实现高斯整数的GCD算法了:

6. 支持多项式

现在我们的GCD算法可以处理整形和高斯整数了,但是GCD还可以处理更多的问题。例如实数域上的一元多项式。先给出多项式的一个简单外观:

这是一个简单polynomial的实现,还有一些copy assign的接口和实现等,受限于篇幅,这里就不给出详细列出了。为了让GCD算法在这里可以正常工作,我们需要实现一个多项式除法,来求解余式。这里选择长除法作为一个简单的示例。

有了求余式的算法,针对多项式的GCD算法,就可以实现了:

6. 抽象统一的外观

代码写到这里,可以发现我们已经实现的几个GCD的版本,算法的步骤几乎都是相同的,都是辗转余,并以b为零作为终止条件。不同的只是类型。我们可以从这里开始使用泛型编程的技术进行抽象了。抽象的统一外观,可以如同下列代码:

算法的核心在于remainder能够正确操作,所以还需要对remainder进一步的完善,使得它可以正确处理全部的整形,高斯整数和实数域的一元多项式:

这里使用了C++模板的SFINAE特性,使用std::enable_if来控制不同静态分支的生成。也就是说对于普通整形,上面的版本才会被实例化;而对于高斯整数,中间的版本才会被实例化;对于实数域的多项式,下面的版本才会生成实际的代码。剩下一个判断T类型是否是高斯整数或者多项式的元函数了。

元函数是编译期对类型和编译期常量计算的函数。例如std::is_integral可以返回一个类型是否是整形。而我们这里判断一个类型是否是高斯整数,可以先判断这个类型是否是std::complex的一个实例化类型,并且std::complex的模板参数T必须是一个有符号整形。std::conjunction是编译期的逻辑与元函数。关于模板元编程的话题,我们会在其他系列的文章详细讨论。

7. 抽象能推广到多远?

到目前为止,我们抽象出了一个GCD算法的外观,那么这个抽象可以推广到多远?MGP上告诉我们,GCD可以推广到整个欧几里得整环。那么,我们可以得到一个目前为止,最通用的GCD算法的外观。

constexpr是C++11引入的编译期求值的关键字,remainder和quotient都应该加上constexpr修饰符,并且GCD需要处理的所有类型,都应该支持constexpr构造与operator. 除了我们目前的多项式的实现需要做比较大的更改来支持constexpr,数学中的大部分数值类型添加编译期的支持,对目前C++17标准不是一件难事。
template< EuclideanDomain T >是C++将要到来的新标准Concepts. EuclideanDomain对类型T有一系列的约束要求,有了这个强大的工具,我们可以很容易地对一个类型集合做抽象了。这里不详细展开Concepts的细节,不过可以给出将来可能的EuclideanDomain Concepts的实现。先看看欧几里得整环的定义:
1. 是一个整环;
2. 定义了带余除法:a == quotient(a, b) * b + remainder(a, b);
3. 存在范数norm(a), 并且满足以下条件:
* let norm(a) == 0, thus a == T{0},
* let b != 0, thus norm(a * b) >= norm(b),
* 余式的范数小于除式的范数: norm(remainder(a, b)) < norm(b).

由公理可知,remainder的范数一定小于除式的范数,所以GCD算法的迭代肯定会终止。受篇幅所限,这里假设已经有了整环IntegralDomain的Concepts实现,那欧几里得整环可能的实现如下:

目前,concepts标准还处于TS状态,所以还只能用C++的SFINAE进行简单的模拟。concepts相对于SFINAE,除了可以对类型所具有的语法层面上的约束之外,还有语义的约束,语义的约束又可以称为契约编程。这种机制可以极大的增强GP的抽象能力。

8. 小结

鄙文通过简单实现了整数,高斯整数和多项式的GCD算法,从中抽象出了通用GCD算法的外观,并将其适用到了欧几里得整环。可以看出GP的抽象过程,与数学理论与模型的推广过程非常相似。篇幅所限,对GP和OOP抽象的分析与比较,放在下一篇中论述。

C++极简的插件化抽象工厂(PAF)

PAF a C++ pluginable abstract factory library

一个极简的插件化抽象工厂.

这是一个简单的插件化抽象工厂.

主要用在面向接口编程, 解耦接口实现者和使用者.

对于实现方: 主要提供非侵入式的注册组件方式,以及完全隔离组件功能,支持跨源文件跨模块重构.

对于使用方: 提供了非常便捷的使用方式.
包含如下特性:
0. 类型安全.仍然能在构造中使用参数,仍然能享受编译期类型安全检查.
1. 扁平化.无依赖只要包含接口头文件,即可使用.无需额外依赖.
2. 额外提供更好用的单例和更强大的全局共享对象.
3. 支持优雅退出检查,循环引用检查.
4. C ABI 友好.
5. 可扩展IOC支持.

sample:

I have a interface and it‘s implemented as below, with a method bar:

with PAF, we need only three line noninvasive code

wherever needed (cross-source/cross-module), we just need to include the interface file for foo_i. (consistent with the interface usage scenario, no additional actions are required).

this is magic show time, which creates local objects with parameter.

the whole process can also rely on the compiler to check for errors. objects created through factories are naturally smart pointers.

of course, it also supports the creation of singleton objects:

singleton will be destroyed manually or by factory with same order as statics. it is more flexible and controllable than the general static singleton.

in addition, a more secure pattern is implemented than a single case control: global shared objects. usually as parent of sub objects. to break circle reference and parameter passing to subs.

in particular, global shared objects can be shared across modules, based on the base layer of the abstract factory. shared objects are destroyed only when all module references are destroyed.

based on the above features, you can also support elegant, leak-free exits. Perform leak and circular reference checks during release.

从抽象谈面向对象与泛型编程 part.1

1. 前言

抽象是我们常用的思维过程。一系列事物通过大脑的提炼,归纳和综合,让我们可以从无序中找出有序,从一个个具体的问题中找出通用的解决方法。编程中的抽象是从面相对象的程序设计,后文称为OOP,开始才有了比较完善的语言支持。如今软件复杂程度越来越高,OOP一直都是解决复杂软件设计的重要方法。使用OOP可以屏蔽各个任务具体的细节,抽象出一个简化而统一的流程,从而降低复杂软件开发的难度。这是一个建模的过程,也是OOP能够成功解决问题的原因。但是OOP的抽象能力是有限的,OOP的滥用反而会使复杂度无控制地上升。鄙文会浅讨OOP抽象适用的场景,并会阐述为什么作者觉得everyting is object是一个错误的设计。这个系列的文章后面还会探讨另外一种抽象能力更强大的范式,泛型编程GP。鄙文不是引战OOP与GP孰优孰劣,而是以抽象为线索,浅讨如何编写出能应付更加复杂程序的方法。

2. 传统OOP中的抽象

OOP有一个经典的案例。鱼会游泳,鸟会飞行,而它们都是动物。于是用OOP就很自然的有了下面的代码:

许多文章和书籍对OOP都是这样教的。从animal继承以扩展新的动物类,如哺乳类。从bird或fish继承,以扩展出具体的物种。当然还有许多文章也拿这个例子作为OOP的反面教材。因为当我们要实现飞鱼的时候,飞鱼既可以游泳,也可以飞行,这个继承结构无所适从。那么究竟是哪里出了问题?

熟悉OOP的人会说,这里违反了里氏里氏替换原则(LSP)。飞鱼属于鱼类,但是我们抽象的鱼类无法满足要求。那我们给鱼增加一个fly接口:

这能解决飞鱼这一个情况,但现实中好像还有更多复杂的情况。例如天鹅属于鸟类,但是它既会走路,也会飞行,当然也会游泳。这个时候我们又不得不扩充鸟类的接口。问题似乎变复杂了,绝大部分的鱼并不会飞行,但是为了兼容飞鱼我们为鱼类添加了fly接口。其实,真正的问题在于,我们为了实现一个可以游泳的动物,根据有限的经验抽象出了鱼类,并把这个动物归为鱼类。这种抽象的方式的代价,就是给实现类与抽象类之间,增加了一个is_a的关系,这个关系是一个很强的依赖关系。

对于我们举的这个例子而言,这种错误很容易发现,这是得益于动物这个话题是人们所熟知的。但是现实的问题往往都是未充分了解的,所以我们的抽象会很容易地违反LSP。其次,仅仅使用接口的异同类对事物进行抽象和归属,似乎并不是一个很好的主意。于是OOP有了改良,对行为的抽象看起来要更合理一些。

3. 接口编程与行为抽象

来到了接口编程,我们不再为类别进行抽象了,而是抽象行为。例如,上面的例子,我们不再为动物区分哺乳类,鸟类和鱼类等,也不会为鱼类实现飞鱼或者鲈鱼等其他具体的物种。简单起见,假设动物的行为有飞行,行走和游泳,于是我们抽象出了这三个接口。

飞鱼既可以飞行也可以游泳,我们只要实现这两个行为,飞鱼的问题就很好地解决了:

如果语言不支持多重继承,可以使用组合的方式实现。目前的设计好像能应付所有的动物,甚至能推广得更远。例如,实现飞机也很容易:

现在我们要实现一个功能,让我们实现的飞鱼先飞行再游泳。在OOP中,对象的具体语义是不可知的,例如我们访问飞鱼的fly接口是通过flyable接口对象,对于swim同样是从swimmable对象访问,而我们并不知道flyable和swimmable是否是从飞鱼而来的。OOP在这里会带来运行期的额外开销。呃,这里好像碰到了点什么问题。我们如何保证flyable和swimmable两个接口对象,都来自于同一个飞鱼对象?

在我们继续往下解决这个问题之前,让我们在回想一下,为什么要抽象。抽象是为了屏蔽每个任务具体的细节,提炼出一个统一的处理流程。也就是说,统一的处理流程是我们抽象的目标。而现在的这个先飞行再游泳的功能,无法在我们现有抽象的模型中表达。问题已经很清楚了,是我们的抽象出了问题。这就是OOP在设计的时候一定要避免的误区,我们的抽象是为了得到统一的处理流程,而不是为了适应更广泛复杂的真实世界的模型而过度抽象,遵守KISS很重要。

对于这个例子,如果我们的需求只是希望在程序中,统一控制实体先飞行再游泳,应该这样抽象

如果我们的需求是想统一控制实体的fly和swin行为,并能够灵活地组合这些行为,可以这样抽象:

OOP的抽象的程度应该到此为止,如果问题更复杂可以适当使用设计模式解决,当然设计模式不是鄙文探讨的范围。坚守KISS原则是一件很难的事情。Everything is object,这个极具诱惑和理想主义的咒语,一直在不停地将我们拉入深渊。是的,一直有一条路通往那里。

4. 深渊,从这里开始

下面的设计,不应该归于OOP的范畴了。因为它们为了试图用统一的方法解决一切问题,而且都违背了KISS最基本的教义。首先,如果我们想要继续坚持之前的设计,又希望问题能够得到解决,本着接口统一的原则,我们可以像COM一样,提供一个接口查询的机制:

还有一个解决方案,就是使用发送消息:

以上两个方法很类似,就放在一起讨论。两个方法都引入了一个共同的公共基类,object_base. 对!Everything is object就从这里开始了。第一种方法提供了一个统一的接口查询机制,而后者提供了一个统一的发送消息机制。两者都需要全局唯一的ID来区分不同的接口或者消息。接口查询的方式会带来接口对象不安全强转,而发送消息会招致不安全的数据转换。就目前而言所引入的公共基类和全局ID的负载,还有不安全的代码都不是很严重的问题。例如后者,可以在对象语义完整的时候,创建代理来绑定操作,从而抵消不安全的代码。最严重的问题是,OOP在这里已经荡然无存了。没有了接口的约束,之前抽象的模型也毫无意义了,这于过程式编程毫无差别。如此抽象的结果,却是写出了等同之前毫无抽象的代码,这种设计难道不是一个悖论吗?

深渊也是有底部的,我们继续把查询接口与查询消息推广,其实就是现代语言都提供的反射。反射是一种内省机制,可以让我们查询对象的成员方法和成员数据。当然,与其这样使用反射,何不去使用一门动态语言?

5. 小结

OOP使用的接口抽象的方法,本质上是围绕接口进行的归类方法。这种方法可以做到统一调度流程,屏蔽具体的细节,但同时也许多代价:
1. 围绕接口进行抽象,只利用了语法约束,但丢弃了语义;
2. 归类引入了很强的is_a的依赖关系,而程序中的归类结果通常与现实中经验相差甚远;
3. 对于静态类型,有很明确的编译期的语言,OOP把负担都扔给了运行期;

OOP有着适中的抽象能力,用来对现实问题的简化模型进行抽象,而everything is object是OOP的滥用。

下一篇,我们将讨论静态强类型语言所擅长的泛型编程。GP具有比OOP强大许多的抽象能力,它比OOP更适合于抽象复杂的模型。我们会以最大公约数算法为线索,讨论GP在C++编程语言中的实践方法。

解耦利器function message bus

需求1

能把模版函数和一个key注册起来以便后面使用,能把参数不同的函数和一个key注册起来以便后面使用。
c++中没有这样的一个容器可以存放模版函数和参数类型不同的函数。

需求2

对象A和对象B相互调用,耦合性很强,如何消除这种耦合性;
对象A和对象B没有任何关系,但A希望用B里面的方法,但二者又不适合直接关联起来。

如果你碰到这两个需求中的任何一个,那么你就需要function message bus。

作用

1.作为一个万能的函数注册器,可以注册任意类型的函数(除了重载函数);

2.解耦对象之间的调用关系

例子

上面的例子展示了如何注册不同类型的函数,包括普通(模版)函数,lambda和成员函数,也支持函数返回值。

因为函数的调用者不必知道被调用者,二者都依赖于function_msg_bus,所以你就可以通过它来解耦对象之间的调用关系。

具体代码

github