C++泛型idioms之一: trait

刚着手写这篇文章的时候,实在不知道该从何处下笔,关于泛型技术有太多的东西可写。

这里我不打算详细讲解关于泛型,关于template的语法细则,单单这一部分完全可以写一本书出来,而且也确实已经有很好的资源可以供我们参考----《C++ Templates - The Complete Guide》。

除了语法,还有什么?Template?STL?是的,对于C++来说,泛型程序设计本身就是基于模块的程序设计,并且已经广泛应用于STL当中。

然而泛型并不仅限于此。

在这个系列文章里,我想展示的是一些或许有些极端的、在实际编程中很少用到的泛型idioms(惯用技法)。然而正如侯捷先生所说:由来技术的推演,并不只是问一句“它有用吗”或“它现在有用吗”可以论断价值的。牛顿发表万有引力公式,并不知道三百年后人们用来计算轨道、登陆月球。个人的成长千万别被群体的惯性绊住脚步。我们曾经鄙夷的别人的“无谓”前卫,可能只因我们故步自封,陷自己于一成不变的行为模式;或因此我们只看到自家井口的天空。深度+广度,古典+前卫,理论+应用,实验室+工厂,才能构筑一个不断进步的世界。

做为泛型的惯用技法,最常用(在STL中随处可见),最简单的恐怕就要数trait了。好了,下面我们先引入trait的定义:

Traits class: A class used in place of template parameters. As a class, it aggregates useful types and constants; as a template, it provides an avenue for that "extra level of indirection" that solves all software problems.

有些抽象吧,下面我们通过代码来说明问题。

假设我们有一组数据存储在数组当中,并且我们有一个指向第一个元素的指针和指向最后一个元素的指针,现在我们要对数组中的所有元素求和,一般的方法就不写了,下面我们直接看它的泛型做法:

  1. template <typename T> 
  2. inline 
  3. T accum (T const* beg, T const* end) 
  4. T total = T();  // assume T() actually creates a zero value 
  5. while (beg != end) { 
  6. total += *beg; 
  7. ++beg; 
  8.  return total; 

这里有一个小问题:如何为正确的类型生成一个0值,以便开始我们的求和计算。对于int和float等内建类型而言,T()都可以符合要求,对于其他的类型,我们稍后讲解。

下面是使用了上文模板的一段代码:

  1.   int num[]={1,2,3,4,5}; 
  2.   // print average value 
  3.   std::cout << "the average value of the integer values is " 
  4.           << accum(&num[0], &num[5]) / 5 
  5.           << '/n'
  6.   // create array of character values 
  7.   char name[] = "templates"
  8.   int length = sizeof(name)-1; 
  9.   // (try to) print average character value 
  10.   std::cout << "the average value of the characters in /"" 
  11.           << name << "/" is " 
  12.           << accum(&name[0], &name[length]) / length 
  13.           << '/n'

程序的输出结果是:

the average value of the integer values is 3
the average value of the characters in "templates" is -5

等一下,这里有个问题不是吗?对于前面部分,程序还算运行正常,但是后半部分由于我们以char类型实例化模板,数组中数据的和超过了它的范围,产生了越界。显然这不是我们想要的结果。

问题已经知道了,但是我们要如何解决呢?一个比较好的办法是对accum()所调用的每个T类型都创建一个关联,所关联的类型就是用来存储累加和的类型。这些关联可以看作是类型T的一个特征,因此,我们也把这个存储累加和的类型称为T的trait。

  1. template<typename T> 
  2. class AccumulationTraits; 
  3. template<> 
  4. class AccumulationTraits<char> { 
  5.   public
  6.     typedef int AccT; 
  7. }; 
  8. template<> 
  9. class AccumulationTraits<short> { 
  10.   public
  11.     typedef int AccT; 
  12. }; 
  13. template<> 
  14. class AccumulationTraits<int> { 
  15.   public
  16.     typedef long AccT; 
  17. }; 
  18. template<> 
  19. class AccumulationTraits<unsigned int> { 
  20.   public
  21.     typedef unsigned long AccT; 
  22. }; 
  23. template<> 
  24. class AccumulationTraits<float> { 
  25.   public
  26.     typedef double AccT; 
  27. }; 

上文中,AccumulationTraits被称为一个trait模板,因为它含有它的参数类型的一个trait。通过特化,我们为特定的类型指定其累加和类型。现在我们修改前面的模板代码如下:

  1.   template <typename T> 
  2.   inline 
  3.   typename AccumulationTraits<T>::AccT accum (T const* beg, 
  4.                                             T const* end) 
  5.   { 
  6.       // return type is traits of the element type 
  7.       typedef typename AccumulationTraits<T>::AccT AccT; 
  8.       AccT total = AccT();  // assume T() actually creates a zero value 
  9.       while (beg != end) { 
  10.           total += *beg; 
  11.           ++beg; 
  12.       } 
  13.       return total; 
  14.   } 

于是,现在例子程序中的输出就完全符合我们的期望了。

对了,我们还留有一个问题不是吗?那就是T()如何解决呢?方法跟上面的累加和trait类似,不过这里我们提供的是一个内联成员函数,来避开C++关于类的静态成员变量的某些限制。

  1. template<typename T> 
  2. class AccumulationTraits; 
  3. template<> 
  4. class AccumulationTraits<char> { 
  5.   public
  6.     typedef int AccT; 
  7.     static AccT zero() { 
  8.         return 0; 
  9.     } 
  10. }; 
  11. template<> 
  12. class AccumulationTraits<short> { 
  13.   public
  14.     typedef int AccT; 
  15.     static AccT zero() { 
  16.         return 0; 
  17.     } 
  18. }; 
  19. template<> 
  20. class AccumulationTraits<int> { 
  21.   public
  22.     typedef long AccT; 
  23.     static AccT zero() { 
  24.         return 0; 
  25.     } 
  26. }; 
  27. template<> 
  28. class AccumulationTraits<unsigned int> { 
  29.   public
  30.     typedef unsigned long AccT; 
  31.     static AccT zero() { 
  32.         return 0; 
  33.     } 
  34. }; 
  35. template<> 
  36. class AccumulationTraits<float> { 
  37.   public
  38.     typedef double AccT; 
  39.     static AccT zero() { 
  40.         return 0; 
  41.     } 
  42. }; 

之后,在accum()中把T()替换为AccT total = AccumulationTraits<T>::zero(); ,到这里大功告成。

在上面的例子中,我们把accum跟AccumulationTraits绑定在一起了,这样当我们改变主意不打算使用AccumulationTraits的时候多少有点不方便。不过没关系,我们可以通过缺省模板参数来搞定它。由于函数模板不支持缺省模板实参,那么,我们只好改用类模板了。

  1.     template <typename T, 
  2.               typename AT = AccumulationTraits<T> > 
  3.     class Accum { 
  4.       public
  5.         static typename AT::AccT accum (T const* beg, T const* end) { 
  6.             typename AT::AccT total = AT::zero(); 
  7.             while (beg != end) { 
  8.                 total += *beg; 
  9.                 ++beg; 
  10.             } 
  11.             return total; 
  12.         } 
  13.     }; 

最后,我们可以引入一个辅助函数,来简化上面基于类的接口:

  1.   template <typename Traits, typename T> 
  2.   inline 
  3.   typename Traits::AccT accum (T const* beg, T const* end) 
  4.   { 
  5.       return Accum<T, Traits>::accum(beg, end); 
  6.   } 

在STL中,trait又被应用在哪里呢?没错,就是迭代器。最常用到的迭代器类型有五种,分别是:value type, difference type, pointer, reference, iterator catagoly。traits这一“特性萃取机”会忠实地将它们原汗原味地榨取出来:

  1.   template <class I>
  2.   struct iterator_traits{
  3.     typedef typename I::iterator_category iterator_category;
  4.     typedef typename I::value_type value_type;
  5.     typedef typename I::difference_type difference_type;
  6.     typedef typename I::pointer pointer;
  7.     typedef typename I::reference reference;
  8.   };

然而,多了这一个间接层,会带来什么好处呢?好处是我们可以拥有特化版本,比如针对原生指针的特化,等等。从而我们使用迭代器时便有了统一的接口。

最后我们来总结一下trait:trait表述了模板参数的一些自然的额外属性(当然它可以不需要通过模板参数传递),它通常都具有很自然的缺省值,可以紧密依赖于一个或多个主参数,并通过trait模板来实现。

关于trait技术我就总结到这里了,如果你意犹未尽的话,可以继续阅读《C++ Templates》,其中包含了更错综复杂的演变过程。

你可能感兴趣的:(C++,iterator,reference,templates,Constants,idioms)