C++元编程和Boost编程库 (C++ Metaprogramming and Boost MPL )中部

3 篇文章 0 订阅
2 篇文章 0 订阅
  • Boost中的MPL库分析

      MPL(Meta-Programming Library)是由David AbrahamsAleksey Gurtovoy为方便模板元编程而开发的库,2003年被Boost吸纳为其中的一员,此后又历经一些大幅度修改,目前已经相当完善,其最新版本于200411月发布。MPL的出现是C++模板元编程发展中的一大创举,它提供了一个通用、高层次的编程框架,其中包括了序列(Sequence)、迭代器(Iterator)、算法(Algorithm)、元函数(Metafunction)等多种组件,具有高度的可重用性,不但提高了模板元编程的效率,而且使模板元编程的应用范围得到相当的扩展。

(一)MPL的组织架构

    一个库的组织形式有时候甚至比它的功能还重要。MPL的作者聪明地借鉴了已经取得巨大成功的STL,在MPL中保留了许多STL的概念,对函数式的编程方式进行了精巧的包装,使得任何熟悉STL的程序员都可以轻易地理解MPL的使用方法。像STL一样,MPL有一个完整的概念体系,对组件作了精心的划分,组件之间相对独立,接口具有通用性,因此将组件之间的依存度和耦合性降低到最小的限度。

STLMPL的组件概念对照如下:

STL概念

 

MPL对应概念

 

容器(Container

序列(Sequence

算法(Algorithm

算法(Algorithm

迭代器(Iterator

迭代器(Iterator

仿函数(Functor

元函数类(Metafunction

配接器(Adaptor

ViewInserter Iterator和相当于仿函数配接器的Binding元函数

配置器(Allocator

无此概念

标准中没有定义

宏(Macro

(二)MPL对其他库的依赖

       MPL是一个高层次的库,它的地位和编译期执行的特殊性决定了它需要一些特殊的辅助设施,并对其他库会有所依赖。

1BoostPreprocessor

       Preprocessor库是一个基于宏的元编程库[7]。预处理器的作用发生在编译以前,所以它比MPL所处的地位还要高端,能够真正实现代码生成。它的典型功能是迭代或者枚举相似的代码段,减少重复而易写错的代码段。MPL中不少代码是近似的,比如在vector的原始代码中,就需要定义n

vectori { … }

 

其中i1迭代到n。为了减少重复劳动,MPL的源代码大量使用自定义和Preprocessor库的宏对重复或具有递推性的内容进行迭代。不过,这也导致源代码难以阅读。比如上面一段展开后的源代码首先是定义在vector/aux_/numbered.cpp的:

然后为了迭代n个上面的类模板,另一个文件则需要重复include这个文件,利用Preprocessor的文件迭代能力可以这样写:

尽管如此,宏还是必需的,它不但避免了重复编写递推式的代码(比如在上述的vector类模板中,n可达50之大,如果完全手写确实是浪费时间),而且还有效控制了代码的生成(比如只需要通过定义迭代次数,即可控制实际生成的类模板个数)。实际上,在使用vector(或其他组件)时,通常我们并不需要每次编译都把这些代码重新生成一次,MPL的作者已经充分考虑到编译效率的问题,所以在MPL的代码中,为每个流行的编译器都建立了一个Processed目录,里面存放着针对编译器特点展开了的代码。仅当定义了BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS时才会强制MPL重新用宏来生成代码。

MPL的作者指出,无论喜欢还是不喜欢,目前宏必须在MPL中扮演着这个不可替代的角色。 

2BoostType Traits

       Type Traits[9]用于验证传递的参数或参数之间是否符合一定的条件,比如可以判定两个参数是否有继承关系、是否可转换等。

3BoostStatic Assert

       Static Assert[8]用于编译时断言,用法类似于C中常用的断言assert()。如果参数经编译时的静态计算为true,则代码能通过编译,不会有任何效果,反之,则会编译出错,并且在使编译信息里面包含有“STATIC_ASSERTION_FAILURE”的字样。

      Static Assert的底层是接受一个bool参数的模板STATIC_ASSERTION_FAILURE,它对true定义一个有成员的特化模板,对false的情况则只有一个特化的声明(无定义)。其接口是一个宏,它产生的代码是sizeof(STATIC_ASSERTION_FAILURE< ... >),显然当参数的实际结果为false时,编译器无法判断STATIC_ASSERTION_FAILURE的长度,因为它尚未定义

因为MPL是只在编译时生效的库,用Static Assert来调试程序是非常合适的,它往往与Type Traits库搭配使用。

4BoostConfig

    像STL一样,由于编译器对标准支持不同,为了使程序库具有移植性,最好是针对环境进行预先的设置。对于MPL这种先锋性的库来说,编译器问题更加让库作者相当头痛。借助于对环境的侦查,可以对预先发现的问题,比如模板的局部特化能力、已知的一些编译器的bug等等,采取相应的补救措施[4]

(三)MPL中的序列

 

1MPL序列概述

    序列是MPL中的数据结构的统称,是MPL中处于中心地位的组件,其地位相当于STL中的容器。MPL对序列的性质进行了细致的划分:

性质

含义 / 主要模型

前向序列Forward Sequence

beginend元函数能够界定其头尾范围的类型序列 / MPL中所有序列

双向序列Bidirectional Sequence

迭代器属于双向迭代器的前向序列 / vectorrange_c

随机访问序列Random Access Sequence

迭代器属于随机访问迭代器的双向序列 / vectorrange_c

可扩展序列Extensible Sequence

允许插入和删除元素的序列 / vectorlist

前可扩展序列Front Extensible Sequence

允许在前端插入和删除元素的可扩展序列 / vectorlist

后可扩展序列Back Extensible Sequence

允许在后端插入和删除元素的可扩展序列 / vectorlist

关联序列Associative Sequence

可以用key值来检索元素的前向序列 / setmap

可扩展关联序列Extensible Associative Sequence

允许插入和删除元素的关联序列 / setmap

整型序列包装器Integral Sequence Wrapper

存放一系列整型常量类(Integral Constant)的一种类模板 / vector_clist_cset_c

不定序列Variadic Sequence

可以用给定元素个数或用不指定元素个数的形式来定义的序列 / vectorlistmap

部分概念在现阶段的MPL版本中其实存在着一些冗余,但这种以概念驱动的程序库却是很清晰的:每一种概念的背后都指明了它所支持的操作。

 

 

2vectordeque

1)概述

    MPL中最简单和最常用的序列就是vector。而deque在目前版本的MPL中相当于vectorvector的实质十分类似于前面示例的类型“数组”,逻辑上是连续线性的,由于它属于不定序列,使用时既可以指定长度,以vectornn>来定义,也可以直接用vectorn>来定义。注意n不能超过宏BOOST_MPL_LIMIT_VECTOR_SIZE的定义,目前MPL的默认值是20vector的特点是支持尾端常数时间的插入和删除操作以及中段和前端线性时间的插入和删除操作。

2)操作

    vector支持的操作无论在命名还是逻辑上基本都与STL 一致,但有一个重大区别,STL的操作函数定义在类的内部,但是限于模板元编程的特殊性,MPL的这些元函数在容器外定义。下表列出它们的用法:

begin::type

 

返回一个迭代器指向v的头部

end::type

 

返回一个迭代器指向v的尾部

size::type

 

返回一个v的大小

empty::type

 

当且仅当v为空时返回一个整型常量类,其值为true

front::type

 

返回v的第一个元素

back::type

 

返回v的最后一个元素

at::type

 

返回v的第n个元素

insert::type

 

返回一个新的vector使其定义为[begin::type, pos), x, [pos, end::type)

insert_range::type

 

返回一个新的vector使其定义为[begin::type, pos), [begin::type, end::type) [pos, end::type)

erase::type

 

返回一个新的vector使其定义为[begin::type, pos), [next::type, end::type)

erase::type

 

返回一个新的vector使其定义为[begin::type, pos), [last, end::type)

clear::type

 

返回一个空的vector

push_back::type

 

返回一个新的vector使其定义为[begin::type, end::type), x

pop_back::type

 

返回一个新的vector使其定义为[begin::type, prior< end::type >::type)

push_front::type

 

返回一个新的vector使其定义为[begin::type, end::type), x

pop_front::type

返回一个新的vector使其定义为[next< begin::type >::type, end::type)

 

 

3)源代码分析

    MPL的源代码有着比较复杂的脉络,主要原因是为了保持移植性,需要针对不同的编译器问题进行规避。比如vector的底层就有三个不同的版本,第一个专门针对不支持模板局部特化的编译器,第二个用于基于类型的序列,第三个是普通版本。在预处理时会根据情况确定使用哪一个版本。它们之间的差异是什么呢?vector0的实现代码中把它们放在了一起,正好可以说明其区别:

 

定义的上半部分是基于类型的版本,下半部分则用于另外两个版本。MPL的参考手册没有说明vector的底层是实现的原理,看起来两种实现之间的差异比较大,其中最重要的差别是vector_tag的用法。vector_tag同样是一个底层的定义,作用应该是传递给各类算法,以区别不同的序列类型。tag的定义同样有两种:

    大概基于类型的版本可以不必实例化一个vector_tag,性能上更优越。从MPL的config配置情况来看,似乎默认只使用基于类型的序列,也就是序列会以v_item作为基类。限于篇幅,这里仅分析基于类型的vector,下文有类似情况时也做同样的处理,不一一展开了。

 

    前面已经指出,vector是一个不定序列,这类序列可以不必指定参数的个数直接使用。C++模板支持不定个数的参数表吗?当然不是。实际上不定序列的效果是通过模板的局部特化来实现的。而能够确定个数的vectorn则是vector的基础。因此首先要看看vectorn(n不等于0时)是怎样实现的:

目前MPL对vector中元素个数的限制是20个以内,所以这段代码一直递推到vector20为止。其中的v_item是一个最底层的结构,它包含的内容类似于上面vector0中的那些成员:

 

很容易联想到前一部分提到的Typelist的Head和Tail结构,但是这里并不像Typelist一样需要一个NullType作结束标记。

至于不定序列vector的定义,则这样给出:

显然可以看出,参数个数之所以可以不定,只是一个特化后的假象而已,针对每一个n,vector都会继承vectorn来制造这种假象。上面代码中的na是一个特殊的类,专用于标明参数未使用。

顺道一提deque,其实现也是通过继承vectorn来实现的,比如:


所以说deque是与vector等价的一个概念。

3.list

1)概述

MPLlist的原理类似于STLlist,其特点是支持前端常数时间的插入和删除操作以及中段和尾端线性时间的插入和删除操作。

 

2)操作

list所支持的操作与vector完全一样,参见vector的操作列表。

 

3)源代码分析

MPLlist的原理上十分类似于上面提到Typelist,其底层的结构l_item是这样定义的:

 

 

另外还需要一个结束标记l_end:

可以看到,l_item接受三个参数,第一个参数表示list的长度,第二个参数相当于上文Typelist实现中的Head,第三个参数相当于Tail。由于递推式的继承关系,listn的终结标记总是为l_end。
list作为不定序列,其实现方式与vector如出一辙,也是通过继承listn,比如对于一个参数的list:

4set

1)概述和操作

set保证了key值在序列中没有重复,对它的插入和删除操作都只需要常数时间。

较之于vectorlistset没有pop_back,pop_front,push_frontpush_backinsert_rangeback等几个操作,但另有几个特殊的操作:

has_key<s,k>::type

如果s中包含一个类型key值为k,则返回一个整型常量类,其值为true

count<s,k>::type

返回skey值为k的元素的序号。

order<s,k>::type

返回skey值为k的元素唯一的整型常量类,其值是一个无符号整数。

at<s,k>::type

at<s,k,def>::type

返回s中含有key值为k的元素。

key_type<s,x>::type

返回类型等同于x

value_type<s,x>::type

返回类型等同于x

erase_key<s,k>::type

返回一个新的set,当中不包括keyk

 

2)源代码分析

MPL的序列都有一个共同点,就是都从一个sequence0开始构造,并以x_item作为存放类型的基础结构。set比起上面的两种序列都来得复杂,它的构造首先从set0开始:

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值