C++11实现一个自动注册的工厂

实现动机
工厂方法是最简单地创建派生类对象的方法,也是很常用的,工厂方法内部使用switch-case根据不同的key去创建不同的派生类对象,下面是一个伪代码。

随着时间的流逝,消息种类越来越多,这个switch-case会越来越长,我在一个开源项目中看到过一百多个case语句,显然这种简单工厂已经不堪负荷,这样的代码对于维护者来说也是一个噩梦。要消除这些长长的switch-case语句是一个需要解决的问题,而自动注册的对象工厂则是一个比较优雅的解决方案。

自动注册的对象工厂遵循了开放-封闭原则,新增对象时无需修改原有代码,仅仅需要扩展即可,彻底地消除了switch-case语句。

实现方法

自动注册的对象工厂的实现思路如下:

1.提供一个单例工厂对象。
2.工厂注册对象(保存创建对象的key和构造器)。
3.利用辅助类,在辅助类对象的构造过程中实现目标对象地注册。
4.利用一个宏来生成辅助对象。
5.在派生类文件中调用这个宏实现自动注册。
其中,需要注意的是,对象工厂并不直接保存对象,而是对象的构造器,因为对象工厂不是对象池,是对象的生产者,允许不断地创建实例,另外,这样做还实现了延迟创建。另外一个要注意的地方是借助宏来实现自动注册,本质上是通过宏来定义了很多全局的静态变量,而这些静态变量仅仅是为了实现自动注册,并没有实际的意义。

下面来看看如何用C++11来实现这个自动注册的对象工厂。

一个单例的对象工厂代码

在C++11中单例的实现非常简单,返回一个一个静态局部变量的引用即可,而且这个方法还是线程安全的,因为C++11中静态局部变量的初始化是线程安全的。工厂内部有一个map,map的值类型为一个function,是对象的构造器。

对象工厂的辅助类的代码

对象工厂的辅助类register_t是工厂类的一个内部模版类,非常简单,只有一个构造函数,这个构造函数中调用了factory的私有变量map_,并往map_中插入了key和泛型对象的构造器。这里用到了C++11的一个新特性:内部类可以通过外部类的实例访问外部类的私有成员,所以register_t可以直接访问factory的私有变量map_。

自动注册的代码

在派生类中调用宏注册自己:

自动注册的关键是通过一个宏来生成静态全局的register_t的实例,因为register_t的实例是用来向工厂注册目标对象的构造器。所以仅仅需要在派生类中调用这个宏就可以实现自动至注册了,而无需修改原有代码。

我们还可以添加智能指针接口,无需让用户管理原始指针,甚至让工厂能创建带任意参数的对象。

Factory最终的实现

示例

总结

使用C++11,仅仅需要几十行代码就可以实现一个自动注册的对象工厂,消除了长长的swithc-case语句,还遵循了开闭原则,简洁而优雅。

完整的代码:https://github.com/qicosmos/cosmos/tree/master/self-register-factory

如果都是hpp的消息是没问题的,如果是h和cpp分开的那种,多个cpp包含含静态变量的头文件会引起的链接问题,这就把静态变量干掉,可以参考这个实现:

https://github.com/qicosmos/cosmos/blob/master/self-register-factory/MessageFatory1.hpp

C++14中的lambda

相比C++11的lambda,C++14的lambda变得更强大可,C++14中可以在capture的时候定义一个初始化的变量:

值初始化:

引用初始化:

我们还可以定义一个可变参数的lambda

更棒的是我们可以实现move capture了

 

一个simple但是够用的单元测试框架.

在项目开发过程中,单元测试必不可少,cpp中常用的单元测试框架有boost.test, gtest等,以boost为例,boost的单元测试框架的一般用法为:

test.cpp:

other.cpp:

实际上我们可以有无穷多个.cpp,里面使用BOOST_AUTO_TEST_CASE去做具体的测试,而不用关心main函数在哪里,错误具体如何统计.

除了这样的常用用法之外,boost还支持测试套件测试模块等大量功能,这些功能在很多时候都没用被用到,正是这些多数时间都没用的功能使得boost的测试框架相当庞大(9841行代码), 严重拖慢编译速度,实际上在日常的测试中有REQUIRE和CHECK的语义就够用了。

boost的这个测试框架还有一个严重的问题,正如注释中所言,它使用异常来中断这是用例,那么如果我们有如下代码:

所以我们现在的需求就是:

1.满足常用的功能(REQUIRE, CHECK, 用户自定义出错时的行为).

2.足够简洁.

3.在任何情况下REQUIRE失败都终止test case.

下面就为大家带来不到200行的header only的一个mini测试框架.

首先它的用法和BOOST差不多:

test.cc:

other.cc:

有木有很棒!然后更棒的是它是一个只有187行的头文件。下面分享一下大概的实现原理。

首先我们需要有一个全局的对象用来记录所有用户通过TEST_CASE宏注册的测试用例,这个全局对象当然只能用函数的局部静态变量来储存,因为如果用类的静态成员的话必须要在cpp里有定义,而直接放在头文件里则会导致多次定义的问题。这个全局对象的定义的简化版本如下:

 

然后我们需要解决用户通过宏TEST_CASE注册测试用例的问题,实际上TEST_CASE的作用是定义一个函数,但是这里有一个问题是,怎么通过这个宏将其定义的函数注册到全局UnitTest中?比如:

这里的TEST_CASE如何将test_func这个函数的地址传给全局对象供以后执行?一般的函数是没法在全局执行的,但是总有些东西是例外,构造函数就是其中的一个,于是我们可以通过构造函数来实现test_func的传递:

其中TestCase构造函数的定义如下:

于是TEST_CASE的工作流程就是, 先声明test_func, 然后定义一个TestCase并将test_func的地址传递过去,然后写出void test_func()让用户自己通过一对{}写出函数体.

另一个要解决的问题是REQUIRE失败的时候怎么终止当前的用例。由于用户的代码可能会捕获异常,所以不能通过异常来实现,在C里实现跳转的常用方式是longjmp,我们也不妨一试:

 

这样,如果REQUIRE失败,那么setjmp就会返回1, 然后continue语句会直接跳到下一个test case执行。

当然通过longjmp方式来跳过测试的办法存在一定争议,因为在C++中longjmp可能跳过非trivial的析构函数,这种情况是UB,但是这里的应用场景比较特殊—–用在单元测试且是致命失败的情况下。目前的想法是在未来的版本中用一个宏来让用户选择是抛异常还是longjmp。

第一次写技术文章,有不清处或不足之处欢迎指出。

详细代码: https://github.com/lucklove/ZBase/blob/master/inc/UnitTest.hh

使用BOOST.SPIRIT.X3的RULE和ACTION进行复杂的语法制导过程

Preface

上一篇简述了boost.spirit.x3的基本使用方法。在四个简单的示例中,展示了如何使用x3组织构造一个语法产生式,与源码串匹配并生成一个综合属性。这些简单的示例中通过组合x3库中的基本语法单元,创建了一些复杂语法单元,也就是非终结符。但这些示例中的语法单元完成的事情还不够,它们只能配合phrase_parse函数告诉我们,与源码是否匹配;并且通过一个简单赋值操作返回一个综合属性。如果我想要在匹配成功的时候完成一些用户自定义的Action,如何完成这种需求?此外,仅使用基本语法单元的组合来实现一个比较复杂的DSL的时候,会让产生式变得非常复杂。这些问题都是鄙文将要解决的问题。

Semantic Action

Semantic Action,姑且翻译成语义作用,是x3提供的一个Unary Parser.它可以包装一个语法单元和一个泛型仿函数。当Action包装语法的单元匹配成功的时候会调用这个泛型的仿函数。首先来看一个例子。

x3::parser类模板重载了operator index. 当一个parser对象以一个泛型仿函数对象为实参,调用这个operator index,parser会返回一个对象。这个对象的类型就是x3::action根据parser和f的类型后实例化的类型。而x3::action内部在发现包装的parser匹配成功以后,就会调用这个泛型仿函数。

泛型仿函数(杜撰的),得满足一个条件。把条件写成一个普通的仿函数如下。

泛型仿函数必须要有一个,具有一个模板参数的operator call. x3中是不关心这个operator call的返回值,因此可有可无。形参类型是一个依赖模板参数的类型,可以有const限定,但会失去对ctx对象的写权限。C++14标准中的Generic Lambda正好可以满足需求,这里使用lambda非常方便。在boost::spirit::qi中使用了boost::pheonix库来实现Semantic Action,但是局限性很大,使用用户自定义的仿函数也需要一些额外的代码来适配,x3的设计要合理多了。

小生详细讨论这个模板参数Context. Context的类型是由parser的综合属性决定的。它本质上是一个tuple. 而我们在operator call中使用的_attr()函数,可以类比为std::get<I>()函数,来获取上下文中传递的数据。_attr()获取的就是包装的子parser的综合属性。这样的函数还有三个,_val()获取rule的综合属性,_where()获取源码匹配串的当前迭代器位置,_pass()获取匹配的结果。_val()与_attr()的细节,鄙文在介绍rule的时候会展开。这里先用一个简单示例展示_pass()的使用方法。

当attribute,在这里就是x3::int_的综合属性,大于100或者小于0的时候把匹配设置为失败。

Rule

x3::rule可以管理组合在一起的基本语法单元,成为一个更复杂的parser。通常在实践中,我们自己定义的DSL语法会很复杂,使用rule管理基本的parsers,就是使用自底向上的方法构建我们的语法产生式,使得层次更加的清晰。下面,依旧以C++ Identifier为示例。

x3::rule一般情况下要定义两个模板参数,第一个模板参数是一个ID,仅仅只是当做ID的参数,可以只是一个前向声明;第二个参数是这个rule的综合属性。第十七行代码中用一个字符串初始化这个rule,给这个rule一个名字,这个行为是可选的,rule保存的字符串可以在调试的时候起到一定的作用。同时,我们可以发现这个rule使用了const修饰符,因为我们的语法都是不变的。定义一个rule对象是申明一个语法,接下来我们要定义它,第十八行的代码就是定义它的方法。那么identifier与identifier_def是如何绑定到一起的?下面我们对x3的源码探究一下,浅尝辄止。

x3::phrase_parser作为整个解析的入口,这个函数会进入x3::phrase_parse_main这个函数。

phrase_parse_main会从这个用Expression Tetmplate构造的静态语法产生式的根节点开始,调用parser的parse方法。实际上rule是从parser使用CRTP模式派生出来的,rule也是一个parser,pharse_parse_main就会进入到rule的parse方法中。

rule的parse成员模板方法调用了一个重要的函数,parse_rule函数。这个函数本来可以用一个通用的模板函数处理所有通用的情况,但是x3并没有这么做。x3的设计是在这里希望用户自己实现一个parse_rule的重载函数。注意,parse_rule是一个非限定性名称(Unqualified Name),因此parse_rule的重载方法因ADL查找的机制,可以定义用户的命名空间下面,rule其实也是定义在用户的命名空间下面。从这里就能够看到x3良好设计的思想。

这里有一个问题,为何不把rule管理的语法单元包含的rule对象中?通过阅读rule的源码可以发现,其成员仅有一个string成员变量。如果大家熟悉Expression Template的设计原理,就可以知道identifier_def是带有复杂的类型信息的。如果我们在rule中包含这个identifier_def,我们只有两种选择。增加一个模板参数<typename RHS>,使用CRTP的方式再一次继承;或者使用一个抽象类型把分派交给运行期。第一种方法,定然会让用户定义rule的时候非常不方便,第二种方法显然也已经违背了x3设计的初衷。然而x3在这里来了一记金蝉脱壳!且看示例代码的第22行。x3使用了rule_defintion来真正管理复杂的Expression Template,它以静态对象的方式在用户命名空间下重载的parse_rule的scope中存在。ADL查找配合重载决议,解析rule的代码进入用户自定义的parse_rule函数中,并由rule_defintion的parse方法继续递归向下!x3此处的设计方法,非常惊艳!!

大部分情况下,我们重载的parse_rule的方法就只有这几行代码了,而且是重复的。x3为了减少大家重载函数的工作量,定义了一个宏BOOST_SPIRIT_DEFINE来完成这项工作。但是有一个约定,rule_definition的对象名只能是rule的对象名跟一个”_def”的后缀。BOOST_SPIRIT_DEFINE还是一个可变参数的宏,可以让我们只使用一个宏就能够定义出我们需要的全部工作。

Problems in MSVC

上午刚刚从群里面的伙伴得知vs2015 update 1已经部分支持Expression SFINAE的特性了,x3的代码是不是可以不用修改就能使用。此时我正在下载安装update 1,稍后验证一下,会在后面的文章给出测试结果。

上一篇中也有一些纰漏。x3在vs2015中不能使用而修改的SFINAE的实现方法有点问题,并不能正确traits出ID是否有on_error和on_success函数,正确的修改做法请参考这一篇博客。我写的SFINAE为什么没有生效,小生还需研究一下,还请知道的大神们不吝赐教。

再提一提BOOST_SPIRIT_DEFINE宏,上一篇的文章中,我们有修改这个宏。官方的示例是允许如下的使用方式的。

以上两种方式在vs2015中也不不能编译通过的,不过使用本文示例中的方法暂时能满足需求。我也会在update1中也验证一下,decltype的bug是不是修复了。

Ending

x3提供了Semantic Action给用户处理复杂的解析行为,还提供了rule给用户管理复杂的语法单元。但是x3并没有把复杂的问题交给用户,而是使用了精妙的设计来规避静态类型带来的问题。下一篇,小生会谈谈spirit的性能问题,如何高效使用spirit的注意事项;还会详细讲述x3的黑科技,自定义parser来扩展x3的功能。

在msvc中使用Boost.Spirit.X3

Preface

Examples of designs that meet most of the criteria for “goodness” (easy to understand, flexible, efficient) are a recursive-descent parser, which is traditional procedural code. Another example is the STL, which is a generic library of containers and algorithms depending crucially on both traditional procedural code and on parametric polymorphism.” Bjarne Stroustrup

先把Boost文档当中引用的Bj的名言搬过来镇楼。小生在这里斗胆也来一句。 Boost spirit is a recursive-descent parser, which is depending on traditional procedural code, static(parametric) polymorphism and expression template.  Procedural Code控制流程,Static Polymorphism实现模式匹配与分派,再加上使用Expression Template管理语法产生式,让spirit充满的魔力。

鄙文对Spirit的性能问题不作讨论,只介绍Spirit.X3的一些基本概念和简单的使用方法,并在最后给出一个简单的示例。后面的一两篇幅,会分析一些性能问题,并介绍如何扩展X3. 

Terminals & Nonterminals

终结符号在X3中代表了一些基本词法单元(parser)的集合,它们通常都是一元的(unary parser),在后面的篇幅中会剖析spirit的源码作详细解释。终结符号在展开语法生成式的时候,是最基本的单位。例如x3::char_匹配一个字符,x3::ascii::alpha匹配一个ascii码的一个字母,x3::float_匹配一个单精度浮点数等,匹配字符串使用了正则表达式引擎。详细请参考字符单元数字单元字符串单元等。

非终结符号通常是由终结符号按照一定的逻辑关系组成而来。非终结符号通过组合终结符号来生成定义复杂的语法生成式。例如x3::float_ >> x3::float与”16.0 1.2″匹配成功,>>表示一个顺序关系。*x3::char_与”asbcdf234″匹配成功,但同样也会与”assd  s  s ddd”匹配成功,在词法单元的世界中空格或者一些自定义的skipper(如注释)会被忽略跳过。详细的参考X3非终结符的文档。

上面我们看到在X3使用终结符与C++的operator来生成非终结符,那么非终结符到底是什么类型。实际上它是使用了expression template,创建了一个静态树形结构的语法产生式。那么展开产生式的过程,就是一个自顶向下的深度优先遍历,碰到非终结符号,x3会尝试匹配其子语法单元直到终结符。

Synthesized Attribute

无论是终结符还是非终结符,在匹配字符串成功以后,它们将字符串作为输入,总会输出的某一个类型的值。这个值就是这个语法单元的综合属性。例如x3::char_的综合属性是char类型的值,x3::float_对应float型数的值。非终结符的属性比较复杂,可以参考组合语法单元的综合属性

除了综合属性外,还有一个继承属性。继承属性同综合属性一样也是某一个类型的值,这个值可能来自于某个语法产生式其他节点的综合属性。例如xml的节点<Node></Node>,在解析</Node>的时候,需要与前面的匹配,这里就是使用继承属性的场景。可惜在x3中继承属性还没有实现,在boost::spirit::qi中有继承属性的实现。小生正在尝试实现继承属性,但是鄙文就不讨论继承属性了。

Start Rule

在编译解析源语言的开始,x3需要知道其语法产生式的起始语法,也就是语法产生式的静态树形数据结构的根节点。整个分析的流程就总根节点开始递归向下进行。而根节点的综合树形可以是代表这个源代码的抽象语法树。我们可以发现X3的词法分析与语法分析是被合并到一趟(One Pass)来完成了。当然,也可以在第一趟只做词法分析,将根节点的综合属性依旧为字符串,然后再做第二趟完成语法分析。

Simple Examples

1.解析”1.2, 1.3, 1.4, 1.5″

x3::float_ >> *(‘,’ >> x3::float_)表示一个float类型的数据后面紧跟若干个(‘,’ >> x3::float_)的组合。在尝试写组合语法产生式的时候,先考虑语法再考虑综合属性。那么这里就要探究一下,这个组合产生式的综合属性是什么。‘,’是一个字符常量,在x3的文档中可以知道,字符串常量x3::lit的综合属性是x3::unused,这意味着它只会消费(consume)源码的字符串而不会消费(consume)综合属性的占位。简而言之‘,’ >> x3::float_中的’,’可以忽略,则其综合属性就是float类型的值。那么整个产生式的综合属性就是std::vector<int>类型的值了,或者其类型与std::vector<int>兼容(fusion.adapt)。

x3::float_ >> *(‘,’ >> x3::float_)可以简化为x3::float_ % ‘,’.

2. 解析”1.2, Hello World”到一个用户自定的综合属性

借助Boost.Fusion库,我们可以把一个struct适配成一个tuple. 宏BOOST_FUSION_ADAPT_STRUCT就把struct user_defined适配成了boost::fusion::vector<float, std::string>.

x3::lexeme是一个词法探测器。词法探测器同样是一个parser,同样有综合属性。lexeme的综合属性是一个字符串值,但是它修改字符串迭代器的行为,在匹配的时候不跳过空格。如果是默认跳过空格的行为,那么*x3::char_会跳过字符串间的空格,匹配的结果将会是”HelloWorld”,这是一个错误的结果;而x3::lexeme[*x3::char_]匹配的结果是”Hello World”.

phrase_parse函数定义在boost::spirit::x3的命名空间下,在这里phrase_parse是一个非限定性名称(unqualified name),使用ADL查找就能正确找到函数的入口。

3. 解析C++的Identifier

C++的identifier要求第一个字符只能是字母或者下划线,而后面的字符可以是字母数字或者下划线;

第一种方法比较直观。x3::char_只匹配一个字符,x3::char_重载的operator call可以罗列其可以匹配的全部字符,别忘了使用lexeme不跳过空格。

第二种方法使用了x3中内置的charactor parser. x3::alpha是一个字母的parser而x3::alnum是字母和数字的parser.

这一种看似更简洁,但是它实际上是错误的。原因在于’_’是一个常量字符,x3::lit是没有综合属性的,所以当我们使用这个parser去解析一个identirier的时候,它会漏掉下划线。

这一个例子会让我们更深刻的理解匹配串与综合属性的关系。虽然x3::raw的重载的operator index中的表达式的综合属性会忽略下划线,但是它匹配的字符串没有忽略下划线!x3::raw探测器,是一个unary parser,其综合属性的类型是一个字符串。它忽略其operator index中parser的综合属性,以其匹配的串来代替!例如,”_foo_1″中x3::lexeme[(x3::alpha | ‘_’) >> *(x3::alnum | ‘_’)]匹配的串是”_foo_1″,其综合属性是”foo1″;identifier_def的综合属性就把”foo1″用匹配串”_foo_1″代替。

4. 解析C++的注释

C++中注释有两种”//”和”/**/”。”//”一直到本行结束都是注释;而”/*”与下一个”*/”之间的都是注释。

operator> 与operator>>都是顺序关系,但是前者比后者更严格。后者由operator>>顺序连接的parser不存在也是可以通过匹配的;但是前者有一个predicate的性质在其中,operator>连接的parser必须匹配才能成功。x3::eol与x3::eoi是两个charactor parser,分别表示文件的换行符与文件末尾符。我们值关心注释匹配的串,在真正的解析中会被忽略掉,而不关心注释语法单元的综合属性。x3::seek是另外一个词法探测器,它的综合属性依旧是一个字符串,它同x3::lexeme一样修改了迭代器的行为,匹配一个串直到出现一个指定的字符为止。

msvc中使用x3

x3使用了C++14标准的特性,如Expression SFINAE(基本上都是它的锅), Generic Lambda等。它使用的大部分C++14的特性在vs2015的编译器上暂时都有实现除了Expression SFINAE. 小生只过了X3官方的例子,发现只用把这些使用了Expression SFINAE的代码改成传统的SFINAE的方法。除此之外还有Boost.Preprocessor库与decltype一起使用的时候在msvc14.0的编译器下有bug的问题。顺便喷一下微软,msvc都开始实现C++17的提案了,竟然连C++11的标准都还没有全部搞定!

1.修改<boost/spirit/home/x3/nonterninal/detail/rule.hpp>中的代码

 2. 修改<boost/spirit/home/x3/support/utility/is_callable.hpp>中的代码

3. 修改<boost/spirit/home/x3/nonterninal/rule.hpp>中的宏BOOST_SPIRIT_DEFINE为如下实现


修改出1、2都是因为Expression SFINAE在msvc中还没有实现。而修改处3的原因是在使用BOOST_SPIRIT_DEFINE貌似与decltype有冲突,小生写了一些测试代码,最后把问题锁定在decltype(rule_name)作为形参类型的用法上。这里在gcc上编译是没有问题的,应该是msvc对decltype的支持还不完全。BOOST_SPIRIT_DEFINE涉及到x3::rule的使用,将在下一篇详细讲解使用方法。

Ending

Boost.Spirit乍看把C++语法弄得面目全非,其实在处理Expression Template的时候,重载operator是最优雅的做法。在UE4的UI框架,还有一些基于Expression Template的数学库中也大量使用了这种技巧。Recursive Descent – 迭代是人,递归是神;Static Polymorphism – 形散而神不散。而Expression Template应用在其中,就像是前面两者的躯骨框架。但是Expression Template如果构建特别复杂的语法产生式,也会使得编译器负担很重,降低编译速度,甚至导致类型标识符的长度大于4K!这些问题将在后面的篇幅同Spirit运行期的效率问题一同讨论。 总体而言,小生觉得Spirit依旧是优雅的。

REST RPC架构思想

1.REST RPC是什么?

REST RPC是一个改进版的RPC架构,它是为了解决传统的RPC和REST方案的一些不足之处而生的,它结合了REST API和RPC的优点,同时又克服了REST API和RPC的缺点。我们先来看看传统的RPC和REST API方案的优点和不足之处。

1.1RPC的优点

  • 屏蔽网络细节
  • 易用,和本地调用类似
  • 提供灵活的API
  • 支持多种协议

1.2RPC的缺点

传统的RPC一般是基于protobuf或thrift去实现的,这类实现方式主要有存在这几个问题。

  • 使用麻烦,使用时需要先写一个DSL描述文件,然后用代码生成器生成各种语言代码,如果model类很多的时候,这个工作就很繁琐,工作量也较大。
  • 维护性差,当某些model类需要修改时,必须重新定义和编译,做一些繁琐而重复的工作。
  • 学习成本比较高,使用它们之前先要学习代码生成器如何使用,还要学习复杂的DSL语法定义规则,而这些语法规则并不是通用的,一段时间不用之后又要重新去学习。
  • 最大问题是不能快速响应API升级,当API或者协议演进的时候,需要给客户端提供新的SDK,当多语言的客户端较多时,每加一个接口时都要更新一堆不同语言的SDK,这是维护升级的噩梦。

1.3REST API的优点

轻量级,简单易用。无需要额外的SDK,维护性和扩展性都比较好 。

1.4REST API的缺点

只支持http协议,使用时需要关注http协议和网络层的细节,而http协议比较臃肿。

1.5REST RPC的特点

REST RPC则吸收RPC和REST API的优点,同时又克服了他们的主要缺点,REST RPC的API和REST API类似,服务请求api是字符串形式,支持mian/sub/sub形式的API,使用方便又无须提供专门的SDK,解决了rpc模型类定义复杂繁琐的问题,也解决了多语言sdk更新的问题。因为api是字符串弱类型的,无需专门的多语言支持的sdk包了,还可以快速响应api和协议升级。它同时克服了rest api只能支持http协议的问题,rest rpc可以支持多种协议,http,tcp都可以,把协议和网络细节完全屏蔽,使用者无需关心协议,就像本地调用一样简单。

2.REST RPC的实现形式

REST RPC的实现形式有两种,一种基本形式和一种变体形式,变体形式是为了克服基本形式的缺点。

2.1REST RPC基本形式

REST API的协议是json格式的,调用者需要先将参数序列化为json字符串。 REST RPC的API是通用的,变化的只有服务端提供的API名称和对应的参数对象,使用者传入服务端提供的服务API名称和参数对象对应的json字符串。

通用REST API原型:

  •  客户端调用:

  • 对应的服务端处理程序:

2.2REST RPC变体形式

变体形式只适用于C++或其它支持可变参数的语言,它让RPC API使用更简单。

REST RPC变体原型:

  • 客户端调用:

  • 对应的服务端处理程序

3.REST RPC的缺点

REST RPC虽然解决了REST API和RPC的大部分缺点,但是它属于弱类型的API(字符串形式的),所以无法在编译期检查接口的正确性,只能在运行期检查API的正确性。 一个改进的做法是由客户端的使用者封装API,在API内部将结构体序列化为json,再调用通用的api,这样可以保证在编译期检查API的正确性。另外一个改进方式是使用REST API变体形式,但这种形式只支持C/C++等支持可变参数的语言。

c++14编译期生成初始化的数组和矩阵

c++11的std::array<T,N>是编译期数组,但是不能在编译期进行初始化,借助constexpr和c++14的std::index_sequence可以很容易实现编译期初始化数组和矩阵了。

测试代码:

上面的代码在GCC5.2下正常运行,VS2015中毛病比较多,constexpr auto array_1_20居然认为是非编译期的,vs2015你肿么了?

然后把constexpr去掉,结果生成的矩阵里面的值变得面目全非,vs2015你到底肿么了?

输出20*20的矩阵

Copy Protected by Chetan's WP-Copyprotect.