首页 > 代码库 > STL源代码分析--萃取编程(traits)技术的实现

STL源代码分析--萃取编程(traits)技术的实现

1.为什么要出现?

依照默认认定。一个模板给出了一个单一的定义,能够用于用户能够想到的不论什么模板參数!可是对于写模板的人而言,这样的方式并不灵活。特别是遇到模板參数为指针时,若想实现与类型的參量不一样的实例化。就变得不太可能了!也有时。想禁止此种同样的实例化变得不太可能!

故而出现了,Partial Specialization!

同一时候,在使用void*指针时。能够最大限度的共享代码,降低代码的膨胀!

2.它是什么?事实上,就是用户定义的偏特化。用template<>来说明这是一个偏特化,针对不论什么模板类型的參数做出更进一步的条件限制所设计出来的一种偏特化版本号!说白了,就是模板进行实例化时的一些重载优先选择机制。选择那些最恰当的偏特化模板进行实例化!

3.Partial Specialization的意义:我们为泛化设计中提供一个特化的版本号(也就是将泛化版本号中的某些模板參数赋予明白的指定,以影响模板的实例化类型);

 4.其在STL中的一个应用:Traits编程技法;核心点就是。不论是类实例化模板。还是指针抑或指向常量的指针进行实例化模板,模板的交出的类型都是合乎程序须要的!!!

故而Traits又被侯杰先生称之为“特性萃取机”(不论是类型,还是指针,都能够交出正确的元素类型)。

5.没有偏特化,就没有了所谓的Traits的广泛使用,没有了Traits,STL也未必会成为C++的瑰宝,没有STL的NB,C++也未必会成为语言王者!正是由于小小的偏特化,成就了迭代器的丰功伟绩,使得算法与数据结构进行了分离!

1.Traits编程技法说白了是利用上篇所用到的偏特化概念来实现的!

2.一个非常好地使用就是在迭代器之中;

3.为什么须要?前提是并非全部的模板类型都是class type,有的是原生指针。有的却是指向为常类型的指针const T *

可是这个时候。是无法运用typedef T value_type,来萃取出元素正确的类型的。

4.不论面对的是原生指针int* 还是const int *,都是能够通过traits取出正确的value_type;

5.正是traits这一层的间接层,才使得算法与数据结构分离了!使得迭代器成为一种粘合剂,STL方能共同合作!

6.故而,iterator_traits必须针对传入的类型为指针pointer以及pointer to const时。设计特化版本号。

引子

STL中使用到萃取(traits)编程技术,

迭代器中萃取技术

STL简述

STL(Standard Template Library)是C++泛型编程(template技术)的集大成者, 迭代器在STL中发挥关键的数据. 在STL中有3个重要的概念:

  1. 容器, 包含顺序容器(vector, list)和关联容器(map, set)
  2. 算法, 各种操作容器的函数模版(count, count_if)
  3. 迭代器, 作为算法和容器的桥梁, 让算法独立于容器发展

迭代器的重要作用就是让容器和算法解耦合, 或者说让数据和操作解耦合, 算法利用迭代器作为输入, 从而摆脱对容器详细数据的訪问. 容器生成的迭代器用于遍历容器中的每一个元素, 同一时候避免暴露容器的内部数据结构和实现细节.

这里通过一个算法的样例来展示迭代器的使用:

template <class Iterator, class T>
Iterator find(Iterator begin, Iterator end, const T& value)
{
while (begin != end && *begin != value)
++begin;
return begin;
}

注意这个样例展示的算法find()能够用于各种各样的容器(vector, map…), 试想没有迭代器, 那就须要为每一个容器都实现一个find()算法, 何其繁琐.

简单的迭代器

在迭代器的实现中, 常常须要訪问迭代器所指对象的类型,称之为该迭代器的value type. 利用内嵌类型申明typedef能够轻松实现隐藏所指对象类型, 例如以下迭代器的实现:

templates <class T>
struct Iterator {
typedef T value_type;
...
};

泛型算法就能够通过typename Iterator::value_type来获得value type.

template <class Iterator>
typename Iterator::value_type
getValue(Iterator iter) {
return *iter;
}

注意keywordtypename不可缺少, 由于T是一个template參数, 在它被实例化之前, 编译器不知道T是一个类型还是一个其它的对象,typename用于告诉编译器这是一个类型, 这样才干通过编译.

萃取的概念

在简单的迭代器中, 通过内嵌类型申明非常好的隐藏了所指对象的内部细节, 实现了数据和算法的分离, 可是STL要求支持迭代器的算法, 应该也要支持原生指针, 比方

Int array[4] = {1, 2, 3, 4};
find(array, array+4, 3);

这里存在的一个难题就是原生指针不能内嵌类型申明, 于是这里就须要多一层的封装,萃取编译技术应运而生.
萃取(traits)编程技术,归纳成四个字就是:特性萃取。

在迭代器的上下文中, 就是萃取出迭代器的value type, 能够概念上觉得迭代器所指对象的类型(value type)就是该迭代器的一个特性(traits)

template <class Iterator>
struct iterator_traits {
typedef typename Iterator::value_type value_type;
...
};

有了iterator_traits,就能够改写算法getValue():

template <class Iterator>
typename iterator_traits<Iterator>::value_type
getValue(Iterator iter) {
return *iter;
}

多一层封装的优点在于, iterator_traits是一个C++类模版,能够为原生指针(特殊的迭代器)定义模版偏特化, <<泛型思维>>对模版偏特化做出的定义是: 针对(不论什么)template參数进一步的条件限制所设计出来的一个特化版本号, 而原生指针T*,const T*就是一种偏特化.

template <class T>
struct iterator_traits<T*> {
typedef T value_type;
};

template <class T>
struct iterator_traits<const T*> {
typedef T value_type;
};

如今无论迭代器是自己定义类模板, 还是原生指针(T*, const T*), struct iterator_traits都能萃取出正确的value type

STL迭代器

Value type仅仅是迭代器的一种特性(traits), 在实际情况中, STL迭代器总共定义了5个特性, 为了让用户自己定义迭代器能适用于STL算法, STL做了一个约定, 即写了一个base classstd::terator, 仅仅要自己定义的迭代器继承std::iterator,iterator_traits就能正确的萃取出迭代器的各种特性, 从而让自己定义迭代器融入STL的大家庭, 无缝的使用各种泛型算法:

template < class Category , class Value , class Distance = ptrdiff_t ,
class Pointer = Value*, class Reference = Value&>
struct iterator {
typedef Category category ;
typedef Value value_type ;
typedef Distance difference_type ;
typedef Pointer pointer ;
typedef Reference reference ;
};

对应的iterator_traits定义例如以下:

template < class Iterator > struct iterator_traits {
typedef typename Iterator::value_typevalue_type ;
typedef typename Iterator::difference_type difference_type ;
typedef typename Iterator::pointer pointer ;
typedef typename Iterator::reference reference ;
typedef typename Iterator::iterator_category iterator_category ;
};

template < class T > struct iterator_traits <T* > {
typedef T value_type ;
typedef ptrdiff_t difference_type ;
typedef T* pointer ;
typedef T& reference ;
typedef random_access_iterator_tag iterator_category ;
};

template < class T > struct iterator_traits <const T* > {
typedef T value_type ;
typedef ptrdiff_t difference_type ;
typedef const T* pointer ;
typedef const T& reference ;
typedef random_access_iterator_tag iterator_category ;
};

上述迭代器中涉及其它四个特性, 限于篇幅, 不再累述, 有兴趣的读者自行翻阅下文的參考文献.

类型萃取

萃取(traits)编程技术弥补了C++语言本身的不足, 而STL不过对迭代器的特性做出规范, 制定出iterator_traits.既然该技术如此实用, 就该应用于更加广泛的应用场景, SGI STL(Silicon Graphics System 开发的STL版本号)做出了尝试, 把它应用在迭代器以外的世界, 于是产生了类型萃取的概念.

我们知道, C++自己定义类型有非常多特性(traits), 比方拥有构造函数, 拷贝构造函数, 析构函数. 还有一方面, C++内置类型入整形int, long, 就没有构造函数, 拷贝构造函数, 析构函数. 依据这些特性, 我们就能够採用最有效的措施进行构造和赋值, 比方对内置类型, 根本就不须要调用构造函数和拷贝构造函数, 而直接採用内存处理操作(malloc(),memcpy()), 从而获得最高效率, 这对于大规模并且操作频繁的容器, 有显著的性能提升.

简单样例

为了使用类型中的这些特性, 能够给类型定义一个特性萃取机: __type_traits

struct __true_type {};
struct __false_type {};
Template <class T>
struct __type_traits {
typedef __false_type has_trivial_default_constructor;
typedef __false_type has_trivial_destructor;
};

由于内嵌类型申明没法表示true和false,我们在这里定义结构体表示__true_type__false_type。缺省情况下, 这些特性都依照最保守的值, 接下来再依据详细的情况, 利用模版特化, 对详细的类型设定更加乐观的值. 比方内置int类型的定义模版特化:

template <>
struct __type_traits<int> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_destructor;
};

依据<<STL源代码剖析>>介绍, 某些编译器能够分析程序中的各个类型, 并生成对应的__type_trait模版特化. 当然对于不支持该功能的编译器来说, 手动编写模版特化, 才干定义更加乐观的值. 否则默认值__false_type将生效.

SGI STL类型萃取

SGI STL定义的__type_traits除了包括特性has_trivial_default_constructorhas_trivial_destructor, 还其它的特性, 例如以下所述:

<pre name="code" class="cpp">template <class type>
struct __type_traits {
typedef __true_type this_dummy_member_must_be_first; //为特殊编译器定制

typedef __false_type has_trivial_default_constructor;
typedef __false_type has_trivial_copy_constructor;
typedef __false_type has_trivial_assignment_operator;
typedef __false_type has_trivial_destructor;
};

应用场景-copy

模版函数copy()STL一个泛型算法, 它有许多的模版特化, 目的都是为了在不同的场景下提升性能, 希望在适当的情况下採用雷霆万钧的手段,从而获得最高性能, 比方针对不同的类型, 依据是否有拷贝构造函数, 它就能够有不同的操作:

// 拷贝一个数组, 其元素为随意类型, 视情况採用最有效拷贝手段
template <class T>
inline void copy(T* source, T* destination, int n) {
copy(source destination, n,
typename __type_traits<T>::has_trivial_copy_constructor());
}

// 拷贝一个数组,其元素类型没有trival copy constructors
template <class T>
void copy (T* source, T* destination, int n, __false_type) {...}

// 拷贝一个数组,其元素类型拥有trival copy constructors
template <class T>
void copy (T* source, T* destination, int n, __true_type) {...}

应用场景-内存池中的析构函数

在内存池的实现中, 须要涉及对象内存空间的获取和释放, 普通情况下,释放一个对象的动态内存空间, 须要调用该对象的析构函数, 因此能够定义析构函数模版destruct()来运行析构操作, 还有一方面, 假设该对象不须要析构函数,那destuct()就什么都不干。

template <class T>
void destruct(T& t)
{
typedef typename type_traits<T>::has_trivial_destrutor has_trivial_destructor;
__destruct(t, has_trivial_destructor());
}

template<class T>
__destruct(T& t, __ture_type)
{
// 析构对象T
}

template<class T>
__destruct<T& t, __false_type)
{
// 啥也不干
}

应用萃取技术

枚举类型转化成真实类型

在实际代码编写中, 常常会碰到这种情景: 已知一个枚举常量, 获取相应真实类型, 这就能够通过萃取技术来解决, 比方定义一下枚举常量:

typedef enum FieldType
{
FIELDTYPE_UNDEFINED = 0,
FIELDTYPE_INT8,
FIELDTYPE_UINT8,
FIELDTYPE_INT16,
FIELDTYPE_UINT16,
FIELDTYPE_INT32,
FIELDTYPE_UINT32,
FIELDTYPE_INT64,
FIELDTYPE_UINT64,
FIELDTYPE_FLOAT,
FIELDTYPE_DOUBLE,
FIELDTYPE_CSTRING,
} EFieldType;

能够通过萃取技术来定义萃取机VariableTypeTraits, 来获取枚举类型相应的真实类型:

template<FieldType pt>
struct VariableTypeTraits {
typedef void SyntaxType;
};


上述含义表示,假设pt拥有一个特性void,那么就能够通过VariableTypeTraits<pt>::SyntaxType,把void萃取出来。当然,上述模块仅仅是一个空壳, 由于对于不同的枚举类型来说,每次都从VariableTypeTraits<pt>::SyntaxType萃取出void是没有意义的。

此时, 模版特化就派上用场了, 通过模版特化。就能为每一个枚举类型定义相应的类型:

#define VARIABLE_TYPE_TRAITS_HELPER(pt, type) template<> struct VariableTypeTraits<pt> \ { typedef type SyntaxType; };

VARIABLE_TYPE_TRAITS_HELPER(FIELDTYPE_INT8, int8_t)
VARIABLE_TYPE_TRAITS_HELPER(FIELDTYPE_UINT8, uint8_t)
VARIABLE_TYPE_TRAITS_HELPER(FIELDTYPE_INT16, int16_t)
VARIABLE_TYPE_TRAITS_HELPER(FIELDTYPE_UINT16, uint16_t)
VARIABLE_TYPE_TRAITS_HELPER(FIELDTYPE_INT32, int32_t)
VARIABLE_TYPE_TRAITS_HELPER(FIELDTYPE_UINT32, uint32_t)
VARIABLE_TYPE_TRAITS_HELPER(FIELDTYPE_INT64, int64_t)
VARIABLE_TYPE_TRAITS_HELPER(FIELDTYPE_UINT64, uint64_t)
VARIABLE_TYPE_TRAITS_HELPER(FIELDTYPE_FLOAT, float)
VARIABLE_TYPE_TRAITS_HELPER(FIELDTYPE_DOUBLE, double)

为了避免大量的代码反复, 上述使用了宏定义的实现方式. 定义上述内容之后, 就能够轻松获取枚举常量指向的真实类型了:

void Fun (FieldType ft) {
switch (ft) {
case FIELDTYPE_INT32:
typedef VariableTypeTraits<FIELDTYPE_INT32>::SyntaxType syntaxType;
syntaxType foo;
...
}

}

例子代码

我先把上文中代码片段组织成完整的程序。以加深读者对该使用方法的理解

// Traits_example1.cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

// 枚举类型定义
typedef enum FieldType
{
FIELDTYPE_UNDEFINED = 0,
FIELDTYPE_INT8,
FIELDTYPE_INT32, //为简化问题,仅仅列举两种
} EFieldType;

// 利用Traits编程技巧
template<FieldType pt>
struct VariableTypeTraits {
typedef void SyntaxType;
};
template<>
struct VariableTypeTraits<FIELDTYPE_INT8> {
typedef int8_t SyntaxType;
};
template<>
struct VariableTypeTraits<FIELDTYPE_INT32> {
typedef int32_t SyntaxType;
};
// 測试demo函数
void func(int8_t var)
{
fprintf(stdout, "type: int8_t; value: %d\n", var);
}
void func(int32_t var)
{
fprintf(stdout, "type: int32_t; value: %d\n", var);
}

int main()
{
typedef VariableTypeTraits<FIELDTYPE_INT8>::SyntaxType int8_type;
int8_type int8_a = 1;
func(int8_a);
typedef VariableTypeTraits<FIELDTYPE_INT32>::SyntaxType int32_type;
int32_type int32_b = 2;
func(int32_b);
return 0;
}

真实类型转化成枚举类型

同理能够利用萃取技术实现真实类型到枚举类型的转化:

template <typename T>
struct TypeTraits {
static const FieldType FIELD_TYPE = FIELDTYPE_UNDEFINED;
}

注意这里不能依葫芦画瓢画瓢使用嵌套类型申明(typedef), 由于枚举变量是对象而不是类型, 于是这里使用了类的静态常量来定义FIELD_TYPE, 接下来能够为各个类型定义模版特化,而没有模版特化的类型, 获得到的FILED_TYPE =FIELDTYPE_UNDEFINED

#define TYPE_TO_FIELDTYPE(type, field_type) template<> struct TypeTraits<type> { static const FieldType FIELD_TYPE = field_type; };

TYPE_TO_FIELDTYPE(int8_t, FIELDTYPE_INT8);
TYPE_TO_FIELDTYPE(uint8_t, FIELDTYPE_UINT8);
TYPE_TO_FIELDTYPE(int16_t, FIELDTYPE_INT16);
TYPE_TO_FIELDTYPE(uint16_t, FIELDTYPE_UINT16);
TYPE_TO_FIELDTYPE(int32_t, FIELDTYPE_INT32);
TYPE_TO_FIELDTYPE(uint32_t, FIELDTYPE_UINT32);
TYPE_TO_FIELDTYPE(int64_t, FIELDTYPE_INT64);
TYPE_TO_FIELDTYPE(uint64_t, FIELDTYPE_UINT64);
TYPE_TO_FIELDTYPE(float, FIELDTYPE_FLOAT);
TYPE_TO_FIELDTYPE(double, FIELDTYPE_DOUBLE);

使用起来也比較简单, 调用TypeTraits<int>::FIELD_TYPE就能够获得int类型相应的枚举类型FIELDTYPE_INT32

总结

总结起来, traits编程技巧最大的优点有两点

  1. 能够把利用“特性”来做的推断提升到编译级别,而不是执行时刻再来推断。这样提升了性能。
  2. 能够实现通用实现和数据的解耦合


STL源代码分析--萃取编程(traits)技术的实现