boost源码剖析之:泛型编程精灵type_traits(rev#2)

boost源码剖析之:泛型编程精灵type_traits(rev#2)

 

刘未鹏

C++的罗浮宫(http://blog.csdn.net/pongba)

 

动机

使用traits的动机一般有三种,分派、效率、使某些代码通过编译。

 

分派

下面有一个模板函数,假设一个动物收容组织提供了它,他们接受所有无家可归的可怜的小动物,于是他们向外界提供了一个函数接受注册。函数看起来像这样:

 

template<class T> // T表示接受的是何种动物

void AcceptAnimals(T animal)

{

...  //do something

};

 

但是,如果他们想将猫和狗分开处理(毕竟饲养一只猫和饲养一只狗并不相同。他们可能会为狗买一根链子,而温顺的猫则可能不需要)。一个可行的方法是分别提供两个函数:AcceptDogAcceptCat,然而这种解决办法并不优雅(想想看,注册者可能既有一只猫又有一只狗,这样他不得不调用不同的函数来注册,而且,如果种类还在增多呢,那样会导致向外提供的接口的增多,注册者因此而不得不记住那些烦琐的名字,而这显然没有只需记住AccpetAnimal这一个名字简单)。如果想保持这个模板函数,并将它作为向外界提供的唯一接口,则我们需要某种方式来获取类型T的特征(trait),并按照不同的特征来采用不同的策略。这里我们有第二个解决办法

 

约定所有的动物类(class Cat,class Dog)都必须在内部typedef一个表明自己身份的类型,作为标识的类型如下:

 

struct cat_tag{}; //这只是个空类,目的是激发函数重载,后面会解释

struct dog_tag{}; //同上

 

于是,所有狗类都必须像这样:

 

class Dog

{

public:

  // 类型(身份)标志,表示这是狗类,如果是猫类则为typedef cat_tag type;

typedef  dog_tag  type;

  ...

}

 

然后,动物收容组织可以在内部提供对猫狗分开处理的函数,像这样:

 

// 第二个参数为无名参数,只是为了激发函数重载

template<class T>

void Accept(T dog,dog_tag)

{...}

 

template<class T>

void Accpet(T cat,cat_tag) // 同上

{...}

 

 

于是先前的Accept函数可以改写如下:

 

template<class T>

void Accept(T animal)  //这是向外界提供的唯一接口

{

// 如果T为狗类,则typename T::type就是dog_tag,那么typename T::type()就是创建了一个dog_tag类的临时对象,根据函数重载的规则,这将调用Accept(T,dog_tag),这正是转向处理狗的策略。如果T为猫类,则typename T::typecat_tag,由上面的推导,这将调用Accept(T,cat_tag),即转向处理猫的策略,typename 关键字告诉编译器T::type是个类型而不是静态成员。

Accept(animal, typename T::type()); // #1

}

 

所有类型推导,函数重载,都在编译期完成,你几乎不用耗费任何运行期成本(除了创建dog_tag,cat_tag临时对象的成本,然而经过编译器的优化,这种成本可能也会消失)就拥有了可读性和可维护性高的代码。但是,等等!你说:“traits在哪?typename T::type其实就是traits,只不过少了一层封装而已,如果像这样作一些改进:

 

template<typename T>

struct AnimalTraits

{

typedef T::type type;

};

 

于是,#1处的代码便可以写成:

 

Accept(animal, typename AnimalTraits<T>::type());

 

效率

通常为了提高效率,为某种情况采取特殊的措施是必要的,例如STL里面的copy,原型像这样:

 

// [first,last)区间内的元素拷贝到以dest开始的地方

template<typename IterIn,typename IterOut>

IterOut copy(IterIn first,IterIn last,IterOut dest){

  // ptr_category用来萃取出迭代器的类别以进行适当程度的优化

return copy_opt(first,last,dest, ptr_category(first,dest));

}

 

copy_opt有两个版本,其中一个是针对如基本类型的数组作优化的,如果拷贝发生在char数组间,那么根本用不着挨个元素赋值,基于数组在内存中分布的连续性,可以用速度极快的memmove函数来完成。ptr_category有很多重载版本,对可以使用memmove的情况返回一个空类如scalar_ptr的对象以激发函数重载。其原始版本则返回空类non_scalar_ptr的对象。copy_opt的两个版本于是像这样:

 

// 使用memmove

template<typename IterIn,typename IterOut>

IterOut copy(IterIn first,IterIn last,IterOut dest,

scalar_ptr)

{ ...}

 

// 按部就班的逐个拷贝

template<typename IterIn,typename IterOut>

IterOut copy(IterIn first,IterIn last,IterOut dest,

 non_scalar_ptr)

{ ...}

 

其实通常为了提高效率,还是需要分派。

 

使某些代码能通过编译

这或许令人费解,原来不能通过编译的代码,经过traits的作用就能编译了吗?是的,考虑std::pair的代码(为使代码简洁,忽略大部分)

 

template <typename T1, typename T2>

struct pair

{

T1 first;

  T2 second;

 

// 如果T1T2本身是引用,则编译错误,因为没有“引用的引用

pair(const T1 & nfirst, const T2 & nsecond) // #2

:first(nfirst), second(nsecond) { } 

};

 

这里可以使用一个traits(boost库里面的名字为add_reference)来避免这样的错误。这个traits内含一个typedef,如果add_reference<T>T为引用,则typedef T type;如果不是引用,则typedef T& type;这样#2处的代码便可改成:

 

pair(add_reference<const T1>::type nfirst,

add_reference<const T2>::type nsecond)

  ...

 

这对所有的类型都能通过编译。

 

boost库中的traits

boost中的Traits十分完善,可分为如下几大类:

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值