Boost源码剖析之泛型编程精灵typetraitsrev#2.docx
- 文档编号:6121567
- 上传时间:2023-01-04
- 格式:DOCX
- 页数:18
- 大小:26.93KB
Boost源码剖析之泛型编程精灵typetraitsrev#2.docx
《Boost源码剖析之泛型编程精灵typetraitsrev#2.docx》由会员分享,可在线阅读,更多相关《Boost源码剖析之泛型编程精灵typetraitsrev#2.docx(18页珍藏版)》请在冰豆网上搜索。
Boost源码剖析之泛型编程精灵typetraitsrev#2
boost源码剖析之:
泛型编程精灵type_traits(rev#2)
刘未鹏
C++的罗浮宫(
动机
使用traits的动机一般有三种,分派、效率、使某些代码通过编译。
分派
下面有一个模板函数,假设一个动物收容组织提供了它,他们接受所有无家可归的可怜的小动物,于是他们向外界提供了一个函数接受注册。
函数看起来像这样:
template
voidAcceptAnimals(Tanimal)
{
...//dosomething
};
但是,如果他们想将猫和狗分开处理(毕竟饲养一只猫和饲养一只狗并不相同。
他们可能会为狗买一根链子,而温顺的猫则可能不需要)。
一个可行的方法是分别提供两个函数:
AcceptDog和AcceptCat,然而这种解决办法并不优雅(想想看,注册者可能既有一只猫又有一只狗,这样他不得不调用不同的函数来注册,而且,如果种类还在增多呢,那样会导致向外提供的接口的增多,注册者因此而不得不记住那些烦琐的名字,而这显然没有只需记住AccpetAnimal这一个名字简单)。
如果想保持这个模板函数,并将它作为向外界提供的唯一接口,则我们需要某种方式来获取类型T的特征(trait),并按照不同的特征来采用不同的策略。
这里我们有第二个解决办法:
约定所有的动物类(如classCat,classDog)都必须在内部typedef一个表明自己身份的类型,作为标识的类型如下:
structcat_tag{};//这只是个空类,目的是激发函数重载,后面会解释
structdog_tag{};//同上
于是,所有狗类都必须像这样:
classDog
{
public:
//类型(身份)标志,表示这是狗类,如果是猫类则为typedefcat_tagtype;
typedefdog_tagtype;
...
}
然后,动物收容组织可以在内部提供对猫狗分开处理的函数,像这样:
//第二个参数为无名参数,只是为了激发函数重载
template
voidAccept(Tdog,dog_tag)
{...}
template
voidAccpet(Tcat,cat_tag)//同上
{...}
于是先前的Accept函数可以改写如下:
template
voidAccept(Tanimal)//这是向外界提供的唯一接口
{
//如果T为狗类,则typenameT:
:
type就是dog_tag,那么typenameT:
:
type()就是创建了一个dog_tag类的临时对象,根据函数重载的规则,这将调用Accept(T,dog_tag),这正是转向处理狗的策略。
如果T为猫类,则typenameT:
:
type为cat_tag,由上面的推导,这将调用Accept(T,cat_tag),即转向处理猫的策略,typename关键字告诉编译器T:
:
type是个类型而不是静态成员。
Accept(animal,typenameT:
:
type());//#1
}
所有类型推导,函数重载,都在编译期完成,你几乎不用耗费任何运行期成本(除了创建dog_tag,cat_tag临时对象的成本,然而经过编译器的优化,这种成本可能也会消失)就拥有了可读性和可维护性高的代码。
“但是,等等!
”你说:
“traits在哪?
”,typenameT:
:
type其实就是traits,只不过少了一层封装而已,如果像这样作一些改进:
template
structAnimalTraits
{
typedefT:
:
typetype;
};
于是,#1处的代码便可以写成:
Accept(animal,typenameAnimalTraits
:
type());
效率
通常为了提高效率,为某种情况采取特殊的措施是必要的,例如STL里面的copy,原型像这样:
//将[first,last)区间内的元素拷贝到以dest开始的地方
template
IterOutcopy(IterInfirst,IterInlast,IterOutdest){
//ptr_category用来萃取出迭代器的类别以进行适当程度的优化
returncopy_opt(first,last,dest,ptr_category(first,dest));
}
copy_opt有两个版本,其中一个是针对如基本类型的数组作优化的,如果拷贝发生在char数组间,那么根本用不着挨个元素赋值,基于数组在内存中分布的连续性,可以用速度极快的memmove函数来完成。
ptr_category有很多重载版本,对可以使用memmove的情况返回一个空类如scalar_ptr的对象以激发函数重载。
其原始版本则返回空类non_scalar_ptr的对象。
copy_opt的两个版本于是像这样:
//使用memmove
template
IterOutcopy(IterInfirst,IterInlast,IterOutdest,
scalar_ptr)
{...}
//按部就班的逐个拷贝
template
IterOutcopy(IterInfirst,IterInlast,IterOutdest,
non_scalar_ptr)
{...}
其实通常为了提高效率,还是需要分派。
使某些代码能通过编译
这或许令人费解,原来不能通过编译的代码,经过traits的作用就能编译了吗?
是的,考虑std:
:
pair的代码(为使代码简洁,忽略大部分):
template
structpair
{
T1first;
T2second;
//如果T1或T2本身是引用,则编译错误,因为没有“引用的引用”
pair(constT1&nfirst,constT2&nsecond)//#2
:
first(nfirst),second(nsecond){}
};
这里可以使用一个traits(boost库里面的名字为add_reference)来避免这样的错误。
这个traits内含一个typedef,如果add_reference
pair(add_reference
:
typenfirst,
add_reference
:
typensecond)
...
这对所有的类型都能通过编译。
boost库中的traits
boost中的Traits十分完善,可分为如下几大类:
1.PrimaryTypeCategorisation(初级类型分类)
2.SecondaryTypeCategorisation(次级类型分类)
3.TypeProperties(类型属性)
4.RelationshipsBetweenTypes(类型间关系)
5.TransformationsBetweenTypes(类型间转换)
6.SynthesizingTypes(类型合成)
7.FunctionTraits(函数traits)
由于其中一些traits只是简单的模板偏特化,故不作介绍,本文仅介绍一些技术性较强的traits。
由于traits的定义往往重复代码较多,所以必要时本文仅剖析其底层机制。
所有源码均摘自相应头文件中,为使源码简洁,所有的宏均已展开。
由于traits技巧与编译平台息息相关,某些平台可能不支持模板偏特化。
这里我们假设编译器是符合C++标准的。
在我的VC7.0上,以下代码均通过编译并正常工作。
初级类型分类
is_array(boost/type_traits/is_array.hpp)
定义
//缺省
template
structis_array
{
staticconstboolvalue=false;
};
//偏特化
template
structis_array
{
staticconstboolvalue=true;
};
注解
C++标准允许整型常量表达式作为模板参数,上面的N就是这样。
这也说明出现在模板偏特化版本中的模板参数(在本例中为typenameT,size_tN两个)个数不一定要跟缺省的(本例中为typenameT一个)相同,但出现在类名称后面的参数个数却要跟缺省的个数相同(is_array
使用
is_array
:
value//true(T=int,N=10)
is_array
:
value//false(T=int)
is_class(.../is_class.hpp)
定义
//底层实现,原因是根据不同的编译环境可能有不同的底层实现,我的编译环境为VC7.0,其他底层实现从略。
template
structis_class_impl
{
template
static...:
:
yes_typeis_class_tester(void(U:
:
*)(void));
template
:
no_typeis_class_tester(...);
//ice_and是一个元函数,提供逻辑与(AND)操作
staticconstboolvalue=
...:
:
ice_and<
sizeof(is_class_tester
:
yes_type),//#3
...:
:
ice_not<...:
:
is_union
:
value>:
:
value
>:
:
value
};
template
structis_class
{
//所有实现都在is_class_imp中
staticconstboolvalue=is_class_impl
:
value;
};
注解
:
:
boost:
:
type_traits:
:
yes_type是一个typedef:
typedefcharyes_type;
因此sizeof(yes_type)为1.
:
:
boost:
:
type_traits:
:
no_type则是一个struct:
structno_type
{
charpadding[8];
};
因此sizeof(no_type)为8。
这两个类型一般被用作重载函数的返回值类型,这样通过检查返回值类型的大小就知道到底调用了哪个函数,它们的定义位于“boost/type_traits/detail/yes_no_type.hpp”中。
is_class_impl中有两个static函数,第一个函数仅当模板参数U是类时才能够被实例化,因为它的参数类型是void(U:
:
*)(void),即指向成员函数的指针。
第二个函数具有不定量任意参数列表,C++标准说只有当其它所有的重载版本都不能匹配时,具有任意参数列表(...)的重载版本才会被匹配。
所以,如果T为类,则void(T:
:
*)(void)这种类型就存在,所以对is_class_tester
而如果T不是类,则就不存在void(T:
:
*)(void)这种指针类型,所以第一个函数就不能实例化,这样,对is_class_tester
现在注意#3处的表达式:
sizeof(is_class_tester
:
yes_type)//#3
按照上面的推导,如果T为类,is_class_tester
如果T不是类,则is_class_tester
这正是我们想要的。
一个值得注意的地方是:
在sizeof的世界里,没有表达式被真正求值,编译器只推导出表达式的结果的类型,然后给出该类型的大小。
比如,对于sizeof(is_class_tester
所以声明该函数就够了。
另一个值得注意之处是is_class_tester的两个重载版本都用了模板函数的形式。
第一个版本用模板形式的原因是如果不那样做,而是这样
staticyes_typeis_class_tester(void(T:
:
*)(void));
的话,则当T不是类时,该traits将不能通过编译,原因很简单,当T不是类时void(T:
:
*)(void)根本不存在。
然而,使用模板时,当T不是类时该重载版本会因不能实例化而根本不编译,C++标准允许不被使用的模板不编译(实例化)。
这样编译器就只能使用第二个版本,这正合我们的意思。
而is_class_tester的第二个重载版本为模板则是因为第一个版本是模板,因为在#3处对is_class_tester的调用是这样的:
is_class_tester
如果第二版本不是模板的话,这样调用只能解析为对is_class_tester模板函数(即第一个版本)的调用,于是重载解析也就不复存在了。
“等等!
”你意识到了一些问题:
“模板函数的调用可以不用显式指定模板参数!
”好吧,也就是说你试图这样写:
//模板
template
static...:
:
yes_typeis_class_tester(void(U:
:
*)(void));
//非模板
static...:
:
no_typeis_class_tester(...);
然后在#3标记的那一行这样调用:
is_class_tester(0)//原来是is_class_tester
是的,我得承认,这的确构成了函数重载的条件,也的确令人欣喜的通过了编译,然而结果肯定不是你想要的。
你会发现对所有类型T,is_class
:
value现在都是0了!
也就是说,编译器总是调用is_class_tester(..);这是因为,当调用的函数的所有重载版本中有一个或多个为模板时,编译器首先要尝试进行模板函数实例化而非重载决议,而在尝试实例化的过程中,编译器会进行模板参数推导,0的类型被编译器推导为int(0虽然可以赋给指针,但0的类型不可能被推导为指针类型,因为指针类型可能有无数种,而事实上C++是强类型语言,对象只能属于某一种类型),而第一个函数的参数类型void(U:
:
*)(void)根本无法与int匹配(因为如果匹配了,那么模板参数U被推导为什么呢?
)。
所以第一个版本实例化失败后编译器只能采用非模板的第二个版本。
结果如你所见,是令人懊恼的。
然而如果你写的是is_class_tester
(关于编译器在含有模板函数的重载版本时是如何进行重载决议的,可参见C++Primer的FunctionTemplates一节,里面有极其详细的介绍)。
以上所将的利用函数重载来达到某些目的的技术在type_traits甚至整个boost库里多处用到。
初级类型分类还有:
is_voidis_integralis_floatis_pointeris_referenceis_unionis_enumis_function
请参见boost提供的文档。
次级类型分类
is_member_function_pointer(.../is_member_function_pointer.hpp)
定义(.../detail/is_mem_fun_pointer_impl.hpp)
//缺省版本
template
structis_mem_fun_pointer_impl
{
staticconstboolvalue=false;
};
//偏特化版本,匹配无参数的成员函数
template
structis_mem_fun_pointer_impl : *)()> { staticconstboolvalue=true; }; //匹配一个参数的成员函数 template structis_mem_fun_pointer_impl : *)(T0)> { staticconstboolvalue=true; }; ...//其它版本只是匹配不同参数个数的成员函数的偏特化而已,参见源文件。 template structis_mem_function_pointer { staticconstboolvalue= is_mem_fun_pointer_impl : value; }; 注解 假设你有一个类X,你这样判断: is_mem_function_pointer : *)(int)>: : value 则编译器会先将is_mem_function_pointer的模板参数classT推导为int(X: : *)(int),然后将其传给is_mem_fun_pointer_impl,随后编译器寻找后者的偏特化版本中最佳匹配项为: is_mem_fun_pointer_impl : *)(T0)> 其中R=int,T=X,T0=int。 而该偏特化版本的: : value=true。 次级类型分类还有: is_arithmeticis_fundamentalis_objectis_scalaris_compound 请参见boost提供的文档。 类型属性 is_empty(.../is_empty.hpp) 定义 //如果T是空类,那么派生类的大小就是派生部分的大小即sizeof(int)*256 template structempty_helper_t1 : publicT { empty_helper_t1(); inti[256]; }; structempty_helper_t2 { inti[256]; };//大小为sizeof(int)*256 通过比较以上两个类的大小可以判断T是否为空类,如果它们大小相等则T为空类。 反之则不为空。 这里一个值得注意的地方是: 若定义一个空类E,则sizeof(E)为1(这一个字节是用于在内存中唯一标识该类的不同对象。 如果sizeof(E)为0,则意味着不同的对象在内存中的位置没有区别,这显然有违直观)。 然而如果有另一个非空类继承自E,那么这一个字节的内存就不需要。 也就是说派生类的大小等于派生部分的大小,而非加上一个字节。 //这个辅助类的作用是: 如果T不是类则使用该缺省版本如果T是类则使用下面的偏特化版本。 而判断T是否为类的工作则由上面讲过的is_class<>traits来做。 template structempty_helper { staticconstboolvalue=false; }; template structempty_helper { staticconstboolvalue= (sizeof(empty_helper_t1 }; template structis_empty_impl { //remove_cv将T的constvolatile属性去掉,这是因为在作为基类的类型不能有const/volatile修饰。 typedeftypenameremove_cv : typecvt; staticconstboolvalue= ice_or< empty_helper : value>: : value,//#4 BOOST_IS_EMPTY(cvt) >: : value; }; 注解 在#4处,如果is_class : value为true(即T为类)则empty_helper : value>: : value实际决议为empty_helper 否则T不是类,则采用缺省版本,结果: : value为false。 is_polymorphic(.../is_polymorphic.hpp) is_plymorphic的运作机制基于一个基本事实: 一个多态的类里面会有一个虚函数表指针(一般称为vptr),它指向一个虚函数表(一般称为vtbl)。 后者保存着一系列指向虚函数的函数指针以及运行时类型识别信息。 一个虚函数表指针通常占用4个字节(32寻址环境下的所有指针都占用4个字节)。 反之,如果该类不是多态,则没有这个指针的开销。 基于这个原理,我们可以断定: 如果类X不是多态类(没有vtbl及vptr),则如果从它派生一个类Y,Y中仅含有一个虚函数,这会导致sizeof(Y)>sizeof(X)(这是因为虚函数的首次出现导致编译器必须在Y中加入vptr的缘故)。 反之,如果X原本就是多态类,则sizeof(Y)==sizeof(X)(因为这种情况下,Y中其实已经有了从X继承而来的vtbl及vptr,编译器所要做的只是将新增的虚函数纳入到vtbl中去)。 定义 //当T为类时使用这个版本 template structis_polymorphic_imp1 { typedeftypenameremove_cv : typencvT; //ncvT是将T的constvolatile修饰符去掉后的类型,因为public后不能跟这样的修饰符,该类里没有虚函数 struct
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Boost 源码 剖析 编程 精灵 typetraitsrev