STL traits萃取

一.迭代器萃取机iterator_traits

       为了知道迭代器所指对象的型别,迭代器型别等等其它型别,STL中使用了一种萃取机制,通过iterator_traits来获取它们。但前提是所有迭代器都遵循约定,自行以内嵌型别定义的方式定义出相应型别,所谓内嵌型别定义就是在迭代器内部通过typedef关键字将一些类型定义为约定好的类型名。我们通过一个简单的例子来进行说明。

template<class T>
struct MyIter{
    typedef T      value_type;
    typedef T&     reference;
    typedef T*     pointer;
 
private
    T* _ptr;
    MyIter(T* ptr) : _ptr(ptr){}
};

template<class I>
struct iterator_traits{
    typedef typename I::value_type  value_type;
    typedef typename I::reference   reference;
    typedef typename I::pointer     pointer;
    ...
};

template<class T>
void fun(T a){
    iterator_traits<T>::value_type aCopy = *a;
    ...
    return aCopy ;
}

       为什么不直接获取迭代器内部的内嵌型别呢?而是要通过iterator_traits作为中间件。好处在于iterator_traits拥有特化版本,可以处理原始指针。由于原始指针不是一种class类型,因此无法通过内嵌型别获取其指向的对象类型等信息。而iterator_traits的偏特化版本很好的解决了这些问题,其定义方式如下:

template<class T>
struct iterator_traits<T*>{
    typedef T     value_type;
    typedef T&     reference;
    typedef T*     pointer;
    ...
};

 

二. 迭代器的几种各内嵌型别

2.1 value_type

         迭代器所指对象的型别,其用法在第一部分中已做了叙述。

2.2 difference_type

       用于表示两个迭代器之间的距离,c++中使用ptrdiff_t(#define int ptrdiff_t)作为原始指针的difference_type。迭代器中的该类型也用于表示容器大小,如STL中的count()函数:

template<class I,class T>
typename iterator_traits<I>::difference_type
count(I first, I last, const T& value) {
    typename iterator_traits<I>::difference_type n = 0;
    for(; first != last; ++first){
        if(*first == value)
            ++n;
    }
    return n;
}

2.3 reference 与 const_reference

       在c++中如果要传回一个左值,那么一定是通过引用的形式,如果传回的不是引用而是一个对象,则会产生临时对象,采用的是右值的方式进行传回的。因此要传回左值,返回类型应该为T&或const T&,在迭代器中定义为:

 typedef T&  reference;   typedef const T& const_reference;

2.4 pointer

     pointer是迭代器所指类型的原始指针类型,即其可以指向迭代器所指之物。定义如下:typedef T* pointer;

2.5 iterator_category

       即说明迭代器类型,迭代器有五种类型:1)Input Iterator:只读迭代器,不允许通过这种迭代器改变迭代器所指对象。2)Output Iterator:只写迭代器。3)Forward Iterator:在此种迭代器所形成的区间上可进行读写操作。以上三种迭代器只支持        operator++操作。4) Bidirectional Iterator 双向迭代器,即支持++,也支持--运算符。5)Random Access Iterator:随机迭代器,支持p++,p--,p+n,p-n,p[n],p1-p2,p1 < p2。

       为了在编译期就能根据迭代器类型决定采用哪个函数,而不是等到运行期再去判断。这就要求迭代器型别必须是一个class type而非数值,字符之类的东西。迭代器型别的五个类型定义如下:

struct input_iterator_tag{};
struct output_iterator_tag{};
struct forward_iterator_tag : public input_iterator_tag{};
struct bidirectional_iterator_tag : public forward_iterator_tag{};
struct random_access_iterator_tag : public  bidirectional_iterator_tag {};

       这些类别采用继承机制的原因要从STL算法的命名规则说起,我们知道,迭代器是容器与算法之间的桥梁,而是算法的命名规则是:以算法所能接受的最低阶迭代器类型,来为其迭代器型别参数命名。比如:当一个算法可以接受input_iterator_tag为参数时,那么也可以接受后三种迭代器作为参数,因为input_iterator_tag支持的操作后三种迭代器必然支持。可以将子类种迭代器看作是父类迭代器的加强。比如advance函数的命名:

template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n){ // 以InputIterator进行命名
    // 在算法内部萃取出迭代器类型,来决定调用哪个重载函数
    __advance(i, n, iterator_traits<InputIterator>::iterator_category());
}

      为了能与STL紧密融合,每一种新设计的迭代器都应该包括上述五种相应型别,为了防止漏写,可以继承STL中的iterators class,其定义如下:

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

三.traits编程技巧在其它地方的使用

       除了iterator_traits,在STL内部还使用了__type_traits来萃取型别的特性,这些特性包括:是否有非默认的构造函数?是否有非默认的·拷贝构造函数?是否有非默认的赋值运算符?是否有非默认的析构函数?是否为POD类型?就像在博文《STL 空间配置器(三)》中__uninitialized_copy函数中使用的那样,可以避免对那些不用调用构造函数的型别直接采取memmove操作,从而提高效率。

      __type_traits的设计如下:

struct __true_type{};
struct __false_type{};

template<class type>
struct __type_traits{
    // 防止编译器定义了同名的模板类型
    typedef __true_type    has_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;
    typedef __false_type   is_POD_type;
};

STL先保守的假设都包括非默认构造函数,接着再针对每一个标量设计一个特化版本,举个例子:

template<>
struct __type_traits<char>{
    typedef __true_type   has_trivial_default_constructor; // 只有默认的构造函数
    typedef __true_type   has_trivial_copy_constructor;
    typedef __true_type   has_trivial_assignment_operator;
    typedef __true_type   has_trivial_destructor;
    typedef __true_type   is_POD_type;
};

       类似的还有int, float等等。

       看到这里如果我们细细思考会发现这样一个问题,如果是我们自己定义的类型,那么能否萃取出来呢?以下是在vs2015上进行的一个测试

struct TestBase1 {
	int a;
};

struct TestStruct1 {
	TestBase1 t;
	int64_t num[5];
};

struct TestBase2 {
    TestBase2(){}
	int a;
};

struct TestStruct2 {
	TestBase1 t;
	int64_t num[5];
};

int main()
{
    cout << is_trivial<TestStruct1>::value << std::endl; // 输出: 1(即true)
    cout << is_trivial<TestStruct2>::value << std::endl; // 输出: 0(即false)
    return 0;
}

      我们发现是可以萃取出来的,可这是为什么呢?STL中并没有对自定义结构的特化啊。其实这是由于编译器在背后做了手脚,一些较弱的编译器便不能萃取出来,即使是POD类型,萃取出的特性仍是__false_type。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值