C++模板 - 22(基于traits的泛型程序设计)

熟悉STL的了解traits,特别是type-traits,提供了很多traits,称为元函数,可以基于某个类型计算出所需的类型。

Policy其实也是一种trait,只是后者强调类型,而前者重视行为。Andrei Alexandrescu,Modern C++ Design的作者认为:

policies和policy classes有助于我们设计出安全、有效率且具高度弹性的“设计元素”,所谓policy,就是用来定义一个class 和class template的接口,该接口由下列之一或全部组成:内隐型别定义(inner type definition)、成员函数和成员变量。

下面通过一个简单的例子来说明一下:计算某个序列的和。

template  
T accumulate(const T *begin, const T *end) {
  T sum{};
  while (begin != end) {
    sum += *begin;
    ++begin;
  }
  return sum;
}

上面代码很容易理解,不过其间也有几个隐式约束:序列必须是连续存放的,通过移动begin最终可以到达end,T类型可以通过缺省初始化为零值,其解引用的值能够使用+=运算。

int num[] = {1, 2, 3, 4, 5};
accumulate(num, num + 5); // 15
char hello[] = "hello, world";
accumulate(hello, hello + sizeof(hello) - 1); // -120??

好像有点问题,哦,原因是char的数值范围太小了,作为返回值的类型在累加计算过程中它溢出了。解决方式可以应用traits,定义模板参数T的累加和返回值类型时的关联类型(元函数)。

template  
struct accumulate_traits { using type = T; };

template <> 
struct accumulate_traits { using type = int; };

template <> 
struct accumulate_traits { using type = int; };

template 
using accumulate_traits_t = typename accumulate_traits::type;

template 
accumulate_traits_t accumulate(const T *begin, const T *end) {
  accumulate_traits_t sum{};
  while (begin != end) {
    sum += *begin;
    ++begin;
  }
  return sum;
}

我们定义了一个trait(元函数)accumulate_traits,用来计算类型参数T的返回值类型:缺省返回T自己,对于size小于int的类型(如char, short)则返回int。我们还仿照C++14的做法,定义了acculate_traits_t,方便用户使用。

int num[] = {1, 2, 3, 4, 5};
accumulate(num, num + 5); // 15
char hello[] = "hello, world";
accumulate(hello, hello + sizeof(hello) - 1); // 1160

这回运行一切正常。accumulate模板函数对付基本数值类型足够了,但是

accumulate_traits_t sum{};

这里累加和sum变量的零值初始化的依赖有点让人担心(使用类型的缺省构造,且符合零值语义)。如果用于自定义类型,一不小心这里很容易挂掉。我们未雨绸缪,再添加一个policy,用来定义返回值类型的零值,这样将来如果需要支持的类型有特定的零值构建方式,可以使用特化技术来添加对此类型的支持。

template  
struct accumulate_traits {
  using type = T;
  inline static constexpr type zero_value = 0;
};

template <> 
struct accumulate_traits {
  using type = int;
  inline static constexpr int zero_value = 0;
};

template <> 
struct accumulate_traits {
  using type = int;
  inline static constexpr int zero_value = 0;
};

template 
using accumulate_traits_t = typename accumulate_traits::type;

template 
accumulate_traits_t accumulate(const T *begin, const T *end) {
  accumulate_traits_t sum = accumulate_traits::zero_value;
  while (begin != end) {
    sum += *begin;
    ++begin;
  }
  return sum;
}

如果有个自定义类型Foo,它定义了自己的返回值类型,还有特有的初始化方式,我们可以针对它添加特化

template <> 
struct accumulate_traits {
  using type = typename Foo::ResultType;
  inline static constexpr Foo zero_value = Foo::Initialize(0);
};

这种声明式的(通过添加定义)无缝、透明地功能扩展方式也是STL功能扩展的一种方式,一旦你理解了其间的运作方式,会大大提升你的编程效率。这里模板参数T的关联属性通过trait(返回值类型)或policy(零值的获取方式),可以非常方便地配置、扩展accumulate函数,使之适用于各种类型。比如说这里的+=累和算法,也可以通过定义一个policy来定制化,缺省内置数值类型我们使用+=运算符,对于自定义类型,可以使用特定的累加方式。

最后补充一点:上面traits的应用是固定的,你无法更换,我们可以将traits类型也参数化,这样在特定场合,你可以用其他的traits来替换它,只要你的traits也提供了同样的traits定义。

template >
auto accumulate(const T *begin, const T *end) {
  typename AT::type sum = AT::zero_value;
  while (begin != end) {
    sum += *begin;
    ++begin;
  }
  return sum;
}

我们把traits类型定义为第二个模板参数,它有一个缺省值,就是我们之前定义好的traits类,通常你不需要关注它,除非你有特定的需求,需要替换缺省实现。看着是不是有点眼熟?是啊,STL的容器模板类里就有很多类似的应用。

展开阅读全文

页面更新:2024-04-17

标签:特化   模板   初始化   数值   序列   程序设计   函数   定义   参数   类型   方式

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top