您的位置:首页 > 编程语言 > C语言/C++

C++泛型idioms之一: trait

2008-10-04 10:07 393 查看
刚着手写这篇文章的时候,实在不知道该从何处下笔,关于泛型技术有太多的东西可写。
这里我不打算详细讲解关于泛型,关于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.
有些抽象吧,下面我们通过代码来说明问题。
假设我们有一组数据存储在数组当中,并且我们有一个指向第一个元素的指针和指向最后一个元素的指针,现在我们要对数组中的所有元素求和,一般的方法就不写了,下面我们直接看它的泛型做法:

template <typename T>

inline

T accum (T const* beg, T const* end)

{

T total = T(); // assume T() actually creates a zero value

while (beg != end) {

total += *beg;

++beg;

}

return total;

}

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

  int num[]={1,2,3,4,5};

  // print average value

  std::cout << "the average value of the integer values is "

<< accum(&num[0], &num[5]) / 5

<< '/n';

  // create array of character values

  char name[] = "templates";

  int length = sizeof(name)-1;

  // (try to) print average character value

  std::cout << "the average value of the characters in /""

<< name << "/" is "

<< accum(&name[0], &name[length]) / length

<< '/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。

template<typename T>

class AccumulationTraits;

template<>

class AccumulationTraits<char> {

public:

typedef int AccT;

};

template<>

class AccumulationTraits<short> {

public:

typedef int AccT;

};

template<>

class AccumulationTraits<int> {

public:

typedef long AccT;

};

template<>

class AccumulationTraits<unsigned int> {

public:

typedef unsigned long AccT;

};

template<>

class AccumulationTraits<float> {

public:

typedef double AccT;

};

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

  template <typename T>

  inline

  typename AccumulationTraits<T>::AccT accum (T const* beg,

T const* end)

  {

  // return type is traits of the element type

  typedef typename AccumulationTraits<T>::AccT AccT;

  AccT total = AccT(); // assume T() actually creates a zero value

  while (beg != end) {

  total += *beg;

  ++beg;

  }

  return total;

  }

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

template<typename T>

class AccumulationTraits;

template<>

class AccumulationTraits<char> {

public:

typedef int AccT;

static AccT zero() {

return 0;

}

};

template<>

class AccumulationTraits<short> {

public:

typedef int AccT;

static AccT zero() {

return 0;

}

};

template<>

class AccumulationTraits<int> {

public:

typedef long AccT;

static AccT zero() {

return 0;

}

};

template<>

class AccumulationTraits<unsigned int> {

public:

typedef unsigned long AccT;

static AccT zero() {

return 0;

}

};

template<>

class AccumulationTraits<float> {

public:

typedef double AccT;

static AccT zero() {

return 0;

}

};

之后,在accum()中把T()替换为AccT total = AccumulationTraits<T>::zero(); ,到这里大功告成。
在上面的例子中,我们把accum跟AccumulationTraits绑定在一起了,这样当我们改变主意不打算使用AccumulationTraits的时候多少有点不方便。不过没关系,我们可以通过缺省模板参数来搞定它。由于函数模板不支持缺省模板实参,那么,我们只好改用类模板了。

template <typename T,

typename AT = AccumulationTraits<T> >

class Accum {

public:

static typename AT::AccT accum (T const* beg, T const* end) {

typename AT::AccT total = AT::zero();

while (beg != end) {

total += *beg;

++beg;

}

return total;

}

};

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

  template <typename Traits, typename T>

  inline

  typename Traits::AccT accum (T const* beg, T const* end)

  {

  return Accum<T, Traits>::accum(beg, end);

  }

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

  template <class I>

  struct iterator_traits{

    typedef typename I::iterator_category iterator_category;

    typedef typename I::value_type value_type;

    typedef typename I::difference_type difference_type;

    typedef typename I::pointer pointer;

    typedef typename I::reference reference;

  };

然而,多了这一个间接层,会带来什么好处呢?好处是我们可以拥有特化版本,比如针对原生指针的特化,等等。从而我们使用迭代器时便有了统一的接口。
最后我们来总结一下trait:trait表述了模板参数的一些自然的额外属性(当然它可以不需要通过模板参数传递),它通常都具有很自然的缺省值,可以紧密依赖于一个或多个主参数,并通过trait模板来实现。
关于trait技术我就总结到这里了,如果你意犹未尽的话,可以继续阅读《C++ Templates》,其中包含了更错综复杂的演变过程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: