很多时候,我们需要知道一个类型的信息,例如在下面这种情况下:
template<typename Iterator, typename Dist>
void CuAdvance(Iterator iter, Dist d)
{
if (iter is a random access iterator)
iter += d;
else
{
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
我们希望通过CuAdvance()函数(这里的Cu前缀是为了不与标准库中的函数或类名称冲突)让iterator偏移d距离,如果这个迭代器是random access迭代器的话,我们就可以像数组一样直接让它加上d偏移量,否则,每次就只能偏移一步,所以需要反复使用++或者是–。因此,我们需要有一个判断Iterator模板的类型信息的办法,也就是实现“iter is a random access iterator”这句伪代码的办法,而traits技术的用处正在于此。
我们想让一个类型能够提供关于它本身的信息,最简单的方式自然是在这个类型内添加一个typedef声明。就像下面这样:
templateT>
class CuDeque
{
public:
class iterator
{
public:
typedef randomAccessIteratorTag iteratorCatagory;
};
...
};
其中,randomAccessIteratorTag是一个结构体
struct randomAccessIteratorTag{};
这样的话,我们就可以通过CuDeque::iterator对象的iteratorCatagory来了解它的分类。
但是,这种方式有一个很大的问题,就是无法支持原生指针类型。原生指针不是一个struct或者是class类型的,因此无法内嵌一个typedef声明。因此,光是上面这种方式,并不足够。
计算机领域有这么一句名言“计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决“,在这里也是,我们通过增加一个中间层以及利用模板的偏特化来解决这个问题。
我们增加一个iteratorTraits类(实际上是一个struct),用来提取类型的信息:
template<typename Iterator>
struct iteratorTraits
{
typedef typename Iterator::iteratorCatagory iteratorCatagory;
};
光从上面这部分代码看起来,似乎我们增加这个中间层并没有什么意义。为什么我们增加这个iteratorTraits呢?因为我们还可以对iteratorTraits增加一个针对原生指针的偏特化版本。
template<typename Iterator>
struct iteratorTraits
{
typedef randomAccessIteratorTag iteratorCatagory;
};
由于指针也是支持随机访问的,因此我们把指针的迭代器类型设为randomAccessIteratorTag。
现在,我们就可以用iteratorTraits来提取类型信息了。我们之前的CuAdvance函数实现如下:
template<typename Iterator, typename Dist>
void CuAdvance(Iterator iter, Dist d)
{
if (typeid(iteratorTraits::iteratorCatagory) == typeid(randomAccessIteratorTag))
iter += d;
else
{
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
其中,typeid是C++关键字,可以获取类型的名称。
上面这种方法看起来很完善了,但它实际上并不可行。首先它会导致编译问题。当我们对一个非randomAccessIteratorTag的迭代器使用CuAdvance()时,迭代器是不能进行+=d操作的。虽然我们进行了判断,保证了这个时候绝对不会执行iter += d语句,但编译器必须确保所有源码都有效,即使是逻辑上并不会被执行的代码。
除此之外,还有一个原因是,if语句在运行期才会执行,但我们这里的这一句判断本应该是编译期就要做的事情。想想CuDeque::iterator,它的类型信息在编译期就已经知道了,所以当对它使用CuAdvance()的时候,编译期也就应该知道它的类型信息而确定执行的代码。为了达到这个目的,我们就要使用模板元编程的思想了。
模板元编程(Template metapragramming,TMP)是编写template-based C++程序并执行于编译期的过程。简而言之,就是基于模板,把运行期的工作转移到编译期完成。
具体到traits技术中,我们想要把运行期的工作转移到编译期完成,就要考虑一个编译期的“if-else”判断,这就可以让我们想到函数的重载。
我们把原来的void CuAdvance()的功能扩展成许多个重载的CuDoAdvance()函数来完成:
template
void CuDoAdvance(Iterator iter, Dist d, randomAccessIteratorTag)
{
iter += d;
}
template
void CuDoAdvance(Iterator iter, Dist d, bidirectionalIteratorTag)
{
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
...
...
然后CuAdvance()只需调用CuDoAdvance(),编译器就会根据重载的机制去选择正确的实现版本了。
template<typename Iterator, typename Dist>
void CuAdvance(Iterator iter, Dist d)
{
CuDoAdvance(iter, d, iteratorTraits::iteratorCatagory());
}
到这里,我们就实现了一个提取类型信息的traits类了。
《Effective C++》