STL源码剖析(三)迭代器与traits编程

1. 迭代器概念

1.1 基本概念

  • 一种抽象的设计概念。在《Design Patterns》中有如下定义:

提供一种方法,使之能够依序巡访某个聚合物所含的各个元素,而又无需暴露该聚合物的内部表述方式

  1. 简单来说,迭代器可以让我们访问容器内部的元素,而我们又不需要知道内部是如何构造的
  2. 迭代器将容器与算法撮合在一起
  3. 迭代器最重要的工作就是对operator* 和operator->进行重载

1.2 迭代器设计理念

  • 设计一个容器的迭代器,首先需要对容器的构造非常了解,就将迭代器的开发工作交给容器的设计者,这样就能将所有实现细节封装起来不被使用者看到

2. 引出traits编程

triats,译为特性,traits编程即为特征编程

  • 由推导迭代器相应型别引出traits编程
  1. 利用function template的参数推导机制得出迭代器所指对象的型别
template <class I, class T>
void func_impl(I iter, T t)
{
	T tmp;
	//....
};
template <class I>
inline 
void func(I iter)
{
	func_impl(iter, *iter);
}
int main()
{
	int i;
	func(&i);    //调用模板,自动进行参数推导,导出型别T
}
  • 此方法有一定的局限性,对于函数的返回值类型无法进行推导
  1. 声明内嵌型别,利用该型别得出返回值类型
template <class T>
struct MyIter {
	typedef T value_type;
	T* ptr;
	MyIter(T* p = 0) : ptr(p) {}
	T& oeprator*() const { return *ptr; }
	//...
};
template <class I>
typename I::value_type     //一整行作为func的返回值类型,typename告诉编译器这是一个类型
func(I ite)
{
	return *ite;
}
//....
MyIter<int> ite(new int(8));
cout << func(ite);
  • 此方法亦有一定的局限性,即对于不是class type的迭代器而言,就无内嵌型别,亦无法推导
  1. 能不能找寻一种可以针对某种特定情况做出特定处理的方案呢?答案是可以的,template partial specialization->模板特例化可以做到

3. traits编程

3.1 traits编程技术

参考自pandening的博客

  • traits编程技术就是利用内嵌型别以及通过模板的类型推导机制,获得变量的类型,而后决定dispatch到哪个函数
  • 接下来首先得到迭代器的类型:

3.2 partial specialization(偏特化)

  • 假设定义了一个类模板C:
template <typename T>
class C {...};   //允许接受T为任意类型
  • 那么所谓的偏特化即指对template参数进行进一步的约束,但其本质仍然是模板:
template <typename T>
class C<T*> { .... };   //这个特化版本仅适用于“T为原生指针的情况
//”T为原生指针”即是对模板参数的再一次约束

3.3 traits

  • 专门用来"萃取"迭代器的特性的一个模板类,可根据迭代器的类型不同进行特殊的特化
template <class I>
struct iterator_traits {
	typedef typename I::value_type   value_type;
};
//迭代器是一个原生指针时
template <class T>
struct iterator_traits<T*> {  //偏特化版,迭代器是个原生指针
	typedef T value_type;
};
  • 通过上述的方式进行构造,对于每一个特殊的迭代器均构造其偏特化版,通过“萃取”得出相应迭代器的特性

3.4 常用的迭代器型别

五种:value_type、difference_type、pointer、reference、iterator= catagoly

在模板类中定义如下:

template <class I>
struct iterator_traits {
	typedef typename I::iterator_category  iteerator_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;
};

1. value type:指迭代器所指对象的型别
2. difference type:用来表示两个迭代器之间的距离
3. reference type:引用类型
4. pointer type:指针类型
5. iterator_category:根据移动与施行操作,又可分为5类

  • Input Iterator: 只读迭代器
  • Output Iterator:唯写
  • Forward Iterator:允许“写入型“算法在此迭代器上进行读写操作
  • Bidirectional Iterator:可双向移动
  • Random Access Iterator:涵盖上述所有指针运算能力,随机的迭代器

3.5 dispatch到函数

  • traits,一方面在面对不同的输入类的时候能够提取对应的型别;另一方面如果型别对应有不同的函数实现,则能起到提取型别并分流的作用
  • 以下做了一个测试版本:
#include <iostream>
using namespace std;

//定义tag
struct A {};
struct B : public A {}; //1.以类来定义迭代器的各种分类标签,不仅可以促成重载机制的成功运作
struct C : public B {}; //2.通过继承,可以不必再写“单纯只做传递调用的函数

//定义一个类,里面定义型别
template <class T>
struct Definition {
	typedef  T  Judgement_type;
};

//特性萃取器
template <class Definition>
struct Definition_traits {
	typedef typename Definition::Judgement_type  Judgement_type;
};

//特性萃取器-针对原生指针版本
template <class T>
struct Definition_traits<T*> {
	typedef T Judgement_type;
};

//特性萃取器-针对常量指针版本
template <class T>
struct Definition_traits<const T*> {
	typedef  const T  Judgement_type;
};

//这个函数可以很方便地决定某个迭代器地类型
template <class Definition>
inline typename Definition_traits<Definition>::Judgement_type
Judgement_type(Definition){
	typedef typename Definition_traits<Definition>::Judgement_type JT;
	return JT();
}

//以下是整组func测试函数
template <class Definition>
inline typename Definition_traits<Definition>::Judgement_type
_func(Definition, A) {
	cout << "A tag()" << endl;
	return A();
}

//以下重载函数,根据型别dispatch
template <class Definition>
inline typename Definition_traits<Definition>::Judgement_type
_func(Definition, B) {
	cout << "B tag()" << endl;
	return B();
}

template <class Definition>
inline typename Definition_traits<Definition>::Judgement_type
_func(Definition, C) {
	cout << "C tag()" << endl;
	return C();
}

template <class Definition, class T>
inline T 
_func(Definition, T) {
	cout << "origin ptr" << endl;
	return T();
}

template <class Definition>
inline typename Definition_traits<Definition>::Judgement_type
func(Definition u) {
	typedef typename Definition_traits<Definition>::Judgement_type Judgement_type;
	return _func(u, Judgement_type());
}

int main()
{
	Definition<A> a;
	Definition<B> b;
	Definition<C> c;
	int *p = NULL;

	func(a);
	func(b);
	func(c);
	func(p);
	return 0;
}
  • 理解了这一个实现大概就能理解traits编程的作用

4. traits的第二个应用–__type_traits

iterator_traits萃取迭代器的属性,__type_traits萃取型别的属性,根据不同的型别属性,在编译时期完成函数的派送决定

  • 由于我们在上面已经详述了关于traits编程,在这里就不多分析;来分析一下__type_traits如何判断是否有trivial(不重要的)函数:
    如果至少满足一下3条中的一条:
  1. 显示(explict)地定义了这四种函数,ctor、copy、assignment、dtor
  2. 类里有非静态非POD地数据成员,如string
  3. 有基类
    那么上面四种函数是non-trivial ctor,non-trivial copy…(即有意义的函数),如果该类都是trivial函数,则可在对该型别构造、析构、拷贝、赋值操作时采用最有效率的措施

5. 总结

为什么想写这个总结呢?因为在上述理解traits编程时出现了一点小trouble,可能上面写的有点乱,因此想写个总结重新梳理一下
1.首先了解了迭代器在STL中的重要性以及迭代器这一设计模式
2.由于C++没有获得对象型别的直接方法,因此在尝试了模板参数推导、内嵌型别方法之后,引出了traits编程
3.traits编程,利用内嵌型别以及通过模板的类型推导机制,获得变量的类型,而后决定dispatch到哪个函数
4.traits编程技巧在STL中的应用:iterator_traits,__type_traits

猜你喜欢

转载自blog.csdn.net/qq_38790716/article/details/84108736