C++ 模板元程序(五)

第二章 Traits 与类型操作

我们希望第一章所提的数字例子不会给你这样的样印象:大多数元程序特征就是算术。实际上,在编译期的数值计算相比之下是比较少见的。在本章中,你会了解到那些常常出现的基本概念:元程序就是做类型计算。


2.1 类关联

在 C++ 中,在编译时期可以操作的实体叫做元数据。他们可以分为两种:类型的与非类型的。而所有的分类都可以用做模块的参数。在第一章中所使用到的整数常量则是非类型的一个例子,这种类型也包括在编译时能够确定的几乎一切值:其他的整数类型、枚举值、指针、函数及全局对象的参考,还有指定成员的指针。[1]

[1] 标准也允许将模块做为模板的参数进行传递。这对你来说可能有点绕,在标准中,根据参数被看成是“描述性目的的类型”。尽管模块并不是类型,并且在另一个模板需要一个类型参数时,其不能做为参数进行传递。

    
很容易想像得到对某些非类型元数所进行的计算,但是当你知道也可以对类型进行计算时可能有点意外。让我们来看一个 C++ 标准库中最简单的例子来取得类型计算的直观感受以及其本质:iter_swap,iter_swap 的工作就只是将以两个迭代器的做为参数,并且交换他们所代表的对象。看起来有点像这个样子:

    template <class ForwardIterator1, class ForwardIterator2>
    void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2)
    {
        T tmp = *i1;
        *i1 = *i2;
        *i2 = tmp;
    }


如果此时你不知道 T 从哪里来,那说明你的眼晴真尖。其实它还没有被定义,所以那样写的 iter_swap 也无法编译。好了,我们怎么来命名这个类型呢?

    
2.1.1 使用直接的方式

在你已经知道作者之所举例子的答案的情况下,我们还是要提请你此时把这个答案给忘掉,我们还有几个更深的点需要阐明。相反地,想象着是我们实现了标准库,并且以它的方法来处理迭代器。我们不得不写很多的算法,而它们中的多数要在迭代器与其值之间建立一个关联。我们可以要求所有的迭代器实现都提供一个内嵌的类型叫做 value_type,我们就可以这样直接访问:

    template <class ForwardIterator1, class ForwardIterator2>
    void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2)
    {
        typename                      // (查看语言提示)
          ForwardIterator1::value_type tmp = *i1;
        *i1 = *i2;
        *i2 = tmp;
    }
 

C++ 语言提示
C++ 标准要求在使用一个依赖名字时需要加上 typename 关键词,尽管其本身表明是一个类型。ForwardIterator1::value_type 可能是也可不是一个类型,要依赖于传递下来的特定 ForwardIterator1。可以查看附录B来取得更多信息。


那是一个相当好的处理类型关联的策略,但依然不够通用。特别地, C++ 中的迭代器的实现模就是指针,对于传递普通指针时候,它们却不是一个有效的迭代器。不幸的是,指针并没有内嵌的类型,这种特权只有类才有:

    void f(int* p1, int* p2)
    {
        iter_swap(p1,p2); // 错误: int* 没有成员 'value_type'
    }

2.1.2 舍近求远
我们可以引入一个间接层来解决这个问题。

Butler Lampson
Lampson 的方法在编程中很通用,因此 Andrw Koenig[2] 喜欢将其称为“软件工程的基础理论”(FTSE),我们也许无法为所有的失代器都添加 ::value_type 这样一个成员,但是我们可以把它当成参数装进一个模板中。在标准库中,这样的模板叫做 iterator_traits,有一个非常简单的声明:

 template <class Iterator> struct iterator_traits;

[2] Andrew Koenig 是 Accelerated C++ 的共同作者,也是 C++ 标准委员会的项目编辑。几乎在 Bjarne Stroustrup 的任何一本 C++ 书中感谢部分都会有对其几年来对 C++ 的贡献都有公正的评价。

这里我们把它跟 iter_swap 一起工作:

    template <class ForwardIterator1, class ForwardIterator2>
    void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2)
    {
        typename
          iterator_traits<ForwardIterator1>::value_type tmp = *i1;
        *i1 = *i2;
        *i2 = tmp;
    }

    namespace std
    {
      template <>
      struct iterator_traits<Hector::hands_off>
      {
          typedef int value_type;
         ... 其他四个 typedefs...
      };
    }

iterator_traits 之所以被如此命名是因为他描述了其参数的属性(traits)。在这个例子中,被描述的特性是迭代器的五个相关联的类型:value_type, reference, pointer, difference_type, 以及 iterator_category.

traits 最重要的特征是它给了我们一种将类型以一种非入侵式来关联信息的方式。换句话说,如果你那个爱吵架的同事给了你一些迭代器样的类型叫做 hands_off 来表示 int 类型,你可以给他加上一 value_type 而无需干扰到整个工作组。你所要做的就是增加一个 iterator_traits 的显示特化,而 iter_swap 在请求 value_type 时将会看到 Hector's 的迭代器类型 int: [3]

    namespace std
    {
      template <>
      struct iterator_traits<Hector::hands_off>
      {
          typedef int value_type;
          ... 其他四个 typedefs...
      };
    }

[3] 做为一个模板特化及实例化的一个简单的回顾,可以看本章的末尾。


这个非入侵式的特性正是使对 iterator_traits 使指针能够工作的地方:标准库包含了所有指针的特化版本的 iterator_traits:
    template <class T>
    struct iterator_traits<T*> {
        typedef T value_type;
        ... 其他四个 typedefs...
    };


非常感谢 iterator_traits 间接性,通用函数现在可以统一地关联迭代器的类型了,无论它是一个指针与否。
 
2.1.3 寻找捷径

尽管特化是一个完美而通用的机制,但是在类中添加嵌入类型它并不总是很方便。在特化中伴随着许多的垃圾代码。你不得不关闭你正在使用的名字空间而去打开 traits 模板的名字空间,并且你还不得不自己写特化版本的代码。那并不是有效使用击键的方式:它的内嵌的 typedef 就是唯一有用的信息。

通过仔细考虑,标准库提供了一个捷径,使得迭代器的作者通过在迭代器中提供那些内嵌类型,以此来控制嵌入在 iterator_traits 中的类型。iterator_traits 的主模板[4] 只是从迭代器的成员中抽取类型:

    template <class Iterator>
    struct iterator_traits {
        typedef typename Iterator::value_type value_type;
        ...其他四个 typedefs
    };
    
在这里你看了到了“另一层的间接”:通过直接引用 Iterator:: value_type,这是通过 iter_swap 请求 iterator_traits 中 iterator 的 value_type 来达到的。除非某些特化重载了 iterator_traits 模板,iter_swap 看到的 value_type 与直接访问 iterator 中 value_type 内嵌类型是相同的。
    
[4] C++ 标准将普通的、不同于模板部分或全部特化的声明和定义称为主模板

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值