1. 程式人生 > >STL原始碼剖析(三)迭代器與traits程式設計

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