boost源码剖析之:Tuple Types

Posted on 2008-06-04 15:20 RichardHe 阅读(13) 评论(0)  编辑 收藏 引用 所属分类: [转]
了解tuple的设计目标十分重要。下面两个细节设计目标才是真正需要和体现技术的地方,容我向你阐述它们:tuple中的数据成员的个数应该具有某种动态特性;tuple 必须提供某种途径以获取它内部保存的数值。

动机[1]

假设你有这样一个函数:它接受两个整型数据并返回它们整除的结果,像这样:

 

int DevideInts(int n,int d)

{

   return n/d;

}

 

但是我们可能需要更多信息,比如,余数。函数的返回值已被占用,我们可以为函数加一个参数:

 

int DevideInts(int n,int d,int& Remainder) 

{

    Remainer=n%d; 

    return n/d;

}

 

但是这样的函数形式未免有些拖沓丑陋。我们可以使用std::pair<>来定义函数的返回值类型(顾名思义,std::pair<>可以将两个值凑成一对),像这样:

 

std::pair<int,int> DevideInts(int n,int d)

{

    return std::pair<int,int>(n/d,n%d);

}

 

这是个可行的方案。简洁,优雅。

然而,这个方案只能提供两个返回值的捆绑,如果现在需要返回三个int呢?唔...你可能很快想到这样组织代码:

 

std::pair<int,std::pair<int,int> > someFunc();

 

的确,这也能够工作,但是毕竟不够精致!如果返回值再增加,代码将会愈发丑陋不堪。另一个可行的方案是自己定义一个结构来保存三个乃至更多值,然而随着不同函数的需要你可能需要定义各种不同的类似这样的结构,这太费神了。

所以,我们需要的是一个高度可复用的,能够用来保存任意型别的任意多个变量的类——Tuple Types(Tuple的意思是元组,数组”)。正如你所想象的,泛型正是提供代码复用的最佳手段,它将型别信息抽象出来,直到用户真正使用那些代码时,型别信息才得以落实(所谓具现化”)

Boost库提供了所谓的Tuple Types,它没有std::pair的限制,于是你可以写:

 

//tuple<>目前能够支持多达10个模板参数

boost::tuple<int,int,int> someFunc();

 

事实上tuple能够提供的不止这个,tupleIO流的支持能够允许你写这样的代码:

 

tuple<int,int,int> t(8,9,10);

std::cout<<t;  //输出(8  9  10)

 

tuple甚至还支持类似的流控制,像这样:

 

std::cout << tuples::set_open(‘[‘)

<< tuples::set_close(‘]’)

<< tuples::set_delimiter(‘,’)

<< t;

//输出[8,9,10]

 

好了,你可能已经不耐烦了,毕竟,以上的内容非常浅显。然而我必须要告诉你这些,因为你首先得知道tuple的设计目的才能够去了解它。好在这个枯燥的过程已经结束了。深吸一口气,我们去看一看tuple的设计细节和最本质的东西——源代码。

 

设计目标

首先,了解tuple的设计目标十分重要。上面所讲的只是一个总的设计目标。下面两个细节设计目标才是真正需要和体现技术的地方(并且考虑它们如何能够最佳实现是非常有趣的事情,当然,在你的种种考虑之后,你得承认,Boost库的设计无疑是最精致和高效的),容我向你阐述它们:

 

tuple中的数据成员的个数应该具有某种动态特性。具体的说就是如果你像这样具现化tuple: tuple<int,int> t。则t某种程度上应该只需要sizeof(int)*2大小的内存来存放它的数值,不应该有多余的内存分配。而如果是tuple<int,int,int> t;sizeof(t)某种程度上应该为sizeof(int)*3。当然,你可以利用模板偏特化来实现这一点——为提供不同模板参数个数的tuple实现不同的偏特化版本(也就是说,对提供了N个模板参数的tuple准备的偏特化版本中具有N个数据成员)——但是,想想这样做的代码数量吧!你也可以使用动态分配底层容器的策略,然而那会带来额外的负担,显然不如将数据直接放在tuple对象里,况且底层容器又该如何设计呢?事实上,boost::tuple并没有使用以上任何一种手法,它使用了一种类似Loki[2]里的TypeList设施的手法来定义它的底层容器,这种精致的手法利用了某种递归的概念,极大的减少了代码量。后面我会为你介绍它。

tuple 必须提供某种途径以获取它内部保存的数值。类似的,通过某种编译期的递归,Boost极其巧妙地达到了这个目标。遗憾的是,由于技术上的原因,当你需要获取第N个数据时,你所提供的N必须是编译期可计算出的常量。这也体现出C++泛型缺少一些运行期的特性——是的,C++泛型几乎完全是编译期的。

 

其实,虽然上面我只为你描述了两个设计目标,但是实作时仍会有各种小问题出现。下面的源码剖析中我会一一为你解惑。

好吧,在你发出抱怨声之前,我还是快点转入我们的主题:

 

boost::tuple源码剖析

boost::tuple的实现有许多精妙之处,真是千头万绪不知从何说起。还是从一个最简单的应用展开吧:

 

//请记住它,后面我们将一直围绕这个例子

boost::tuple<int,long,bool> myTuple(10,10,true);

 

以上简单的代码的背后其实发生了很多事,了解了这些事你几乎就了解了关于tuple的一大半奥秘。首先我们肯定想知道tuple的声明是什么样子的,在boost/tuple/detail/tuple_basic.hpp中声明了它,其中也包括tuple几乎所有的实现:

 

template < class T0 = null_type, class T1 = null_type, class T2 = null_type,

class T3 = null_type, class T4 = null_type, class T5 = null_type,

class T6 = null_type, class T7 = null_type, class T8 = null_type,

class T9 = null_type > // null_type是个空类

class tuple;  // 注意这个声明的所有模板参数都有缺省值

 

下面是boost::tuple的定义(也摘自boost/tuple/detail/tuple_basic.hpp):

 

    template <class T0, class T1, class T2, class T3, class T4,

               class T5, class T6, class T7, class T8, class T9>

    class tuple :

      public detail::map_tuple_to_cons<T0, T1, T2, T3, T4,

T5, T6, T7, T8, T9>::type

{

// tuple的定义体十分简单,其中是若干构造函数(将参数转交给基类)和模板赋值操作符

 

}; // 为了凸显重点,以下先讲tuple的基类

 

其实tuple本身的定义并无奥秘和技巧可言,所有秘密都藏在它的基类里面,tuple只是将参数转交给基类处理。下面我为你剖析它的基类:

 

基类大厦的构建

 

构建大厦的脚手架——map_tuple_to_cons<>

在我们给出的极其简单的应用代码中:tuple<int,long,bool> myTuple(10,10,true);其实相当于:

 

tuple<int,long,bool,

null_type,null_type,null_type,null_type,

null_type,null_type,null_type

> myTuple(10,10,true);

 

这是因为tuple的定义中所有模板参数都有缺省值,所以你没有给出值的模板参数自然会被编译器认为是缺省值null_type。这样T0,T1,...,T9分别是int,long,bool,null_type,.....null_type。你发现基类的表现方式非常怪异——是一个map_tuple_to_cons<>中的内嵌型别::type。很自然,你该知道map_tuple_to_const<>的定义,下面就是:

 

    template <class T0, class T1, class T2, class T3, class T4,

               class T5, class T6, class T7, class T8, class T9>

    struct map_tuple_to_cons

    {

    // cons<>是数据的容器,也是所有奥秘所在

    1 typedef cons<

T0, //第一个参数T0被孤立出来

         typename map_tuple_to_cons< //剩下的模板参数后跟一个null_type进入下一轮

T1, T2, T3, T4, T5,T6, T7, T8, T9, null_type

>::type

       > type;

};

 

以及它的一个特化版本:

 

template <>  //这个特化版本是终止某种递归式的自包含定义的关键,后面你会明白

struct map_tuple_to_cons<null_type, null_type, null_type, null_type,

null_type, null_type, null_type, null_type,

null_type, null_type>

   {

      2 typedef null_type type;

};

 

就这么简单。但是它的机理却并非那么明显:上面已经知道T0,T1,...,T9被推导为int,long,bool,null_type,...,null_type(其中省略号表示null_type,下同)。因此tuple的基类:

 

detail::map_tuple_to_cons<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>::type

 

被推导为

 

map_tuple_to_cons<int,long,bool,null_type,...,null_type>::type

 

而根据map_tuple_to_cons的定义1,这其实就是:

 

cons< int,

typename map_tuple_to_cons<long,bool,null_type,...,null_type>::type

>

 

其中的
typename map_tuple_to_cons<long,bool,null_type,...,null_type>::type
再一次涉及1处的typedef,因而它被推导为

 

cons<long,typename map_tuple_to_cons<bool,null_type,...,null_type>::type>

 

所以现在看看基类的定义的形式被推导成为的样子吧:

 

cons<int,

cons<long,

typename map_tuple_to_cons<bool,null_type,...,null_type>::type

> 

> 

 

看出端倪了吗?其中
typename map_tuple_to_cons<bool,null_type,...,null_type>::type
仍然使用1处的typedef,从而为

cons<bool,

typename map_tuple_to_cons<null_type,null_type,...,null_type>::type

> 

 

现在,我们推导至这样一种递归嵌套的模式:

 

cons<int,

cons<long,

cons<bool,

typename map_tuple_to_cons<null_type,...,null_type>::type

> 

> 

> 

 

好了,该是结束这场游戏的时候了,你应该看出来了,map_tuple_to_cons<>准备了一个特化版本来作为这场类似绕口令的递归式包含的休止符。所以,以上的定义再作最后一重推导,使用2处的typedef,将
typename map_tuple_to_cons<null_type,...,null_type>::type
推导为null_type,得到最终的形式:

 

cons<int,cons<long,cons<bool,null_type> > > 

// 这实际上只是为int,long,bool各分配一份空间

 

这就是tuple<int,long,bool>的基类!!现在,你应该可以类似地推导出:如果tuple的形式为tuple<int,long,bool,double>,则其基类为:

 

cons<int,cons<long,cons<bool,cons<double,null_type> > > >

 

这样,随着你给出的模板参数个数的不同(意味着你要求保存的数据的个数不同,tuple的基类竟能够呈现出某种动态的特性(用户提供的模板参数个数的变化(反映用户需要保存的数据的个数)导致cons<>容器的嵌套层数的变化,进而导致tuple的底层内存的分配量也作相应变化)

map_tuple_to_cons<>以一种递归的方式不断将它的第一个模板参数割裂出来,并使tuple的基类呈现像这样的形式:

 

cons<T0,cons<T1,cons<T2,cons<T3,... ... > > > >

 

这种递归当map_tuple_to_cons<>的模板参数都为null_type时才恰好停止,由于map_tuple_to_cons<>不断将第一个模板参数取出,并将剩余的参数在尾部添一个null_type再传递下去。所以当用户给出的模板参数全部被分离出来时,map_tuple_to_cons<>所接受的参数就全部都是null_type了,于是使用其特化版本,其中将内嵌型别type typedefnull_type。从而结束这场递归。

map_tuple_to_cons<>其实在tuple的定义中充当了十分重要的角色,如果没有它的介入,难道还有更简洁美妙的方式来达到这个目的吗?

 

构建大厦的砖石——cons<>

现在,你一定非常想看一看cons<>的定义,下面就是:

 

template <class HT, class TT>

   struct cons {

     typedef HT head_type; // 这是个用户提供的型别

     typedef TT tail_type;   // 这通常是个cons<>的具现体

                             // 以上两个typedef很重要,并非可有可无

     typedef

       typename detail::wrap_non_storeable_type<head_type>::type

stored_head_type;

3   stored_head_type head; // 这是其中第一个数据成员

4   tail_type tail;            // 第二个数据成员

 ...                        // 其成员函数将在后面解释,此处先略去

};

// cons<>还有一个偏特化版本:

template <class HT>

   struct cons<HT, null_type> {

typedef HT head_type;

     typedef null_type tail_type;

     typedef cons<HT, null_type> self_type;

     typedef typename

       detail::wrap_non_storeable_type<head_type>::type stored_head_type;

 

stored_head_type head;

// 注意,不像上面的主模板,这里没有tail成员

     ... // 成员函数将在后面解释

};

 

根据cons<>的定义显示它有两个数据成员:34两处描述了它们,对于第一个数据成员的型别stored_head_type,往它上面看一行,它被typedef:

 

detail::wrap_non_storeable_type<head_type>::type

// head_type又被typedefHT

 

这又是个什么玩意?其实它只是用来侦测你是否使用了void型别和函数类型(所谓函数型别就是像void(int,int)这样的型别,它表示接受两个int型参数返回void的函数的型别,注意,它不同于函数指针型别,后者形式为void(*)(int,int)void(*f)(int,int)定义了一个函数指针f,而void f(int,int)无疑是声明了一个函数f)来具现化tuple,如果是的,那它得采取特殊手段,因为这两种型别不能像int那样定义它们的变量(你见过void val;这样定义val变量的吗)但是你急忙补充这本就应该不能通过编译呀?是的,写void val;这样的语句不应该通过编译,写tuple<void> myTuple;这样的语句也应该不能通过编译。但是,typedef void VoidType;这样的typedef却应该是能够通过编译的,所以typedef tuple<void> voidTupleType;这样的typedef也该能够通过编译。然而如果在cons<>里单纯地写上:

 

HT head;  //如果HTvoid则这将导致编译错误

 

这个成员,则tuple<void>这样的具现化肯定会惹恼编译器(因为它将会发觉cons<>里试图定义一个void型的变量)

所以,对于这种情况,boost使用了wrap_non_storeable_type<>,它的定义是这样的:

 

template <class T>

struct wrap_non_storeable_type {

typedef typename IF<             // IF<>相当于编译期的if...then...else

       ::boost::is_function<T>::value, // 如果为函数类型则特殊处理

non_storeable_type<T>, T      // 如果不是函数类型则type就是T

     >::RET type;

};

 

以及其特化版本:

 

   template <>

struct wrap_non_storeable_type<void> { // 如果为void型也特殊处理

     typedef non_storeable_type<void> type;