1. 程式人生 > >C++之萃取技術

C++之萃取技術

自從C++中引入了template後,以泛型技術為中心的設計得到了長足的進步。STL就是這個階段傑出的產物。STL的目標就是要把資料和演算法分開,分別對其進行設計,之後通過一種名為iterator的東西,把這二者再粘接到一起。設計模式中,關於iterator的描述為:一種能夠順序訪問容器中每個元素的方法,使用該方法不能暴露容器內部的表達方式。可以說,型別萃取技術就是為了要解決和iterator有關的問題的,下面,我們就來看看整個故事。

應該說,迭代器就是一種智慧指標,因此,它也就擁有了一般指標的所有特點——能夠對其進行*->操作。但是在遍歷容器的時候,不可避免的要對遍歷的容器內部有所瞭解,所以,設計一個迭代器也就自然而然的變成了資料結構開發者的一個義務,而這些iterators

的表現都是一樣的,這種內外的差異,對使用者來說,是完全透明的,

第一部分 為什麼要有萃取技術

既然是一種智慧指標,iterator也要對一個原生指標進行封裝,而問題就源於此,當我們需要這個原生指標所指物件的型別的時候(例如宣告變數),怎麼辦呢?

Case1 對於函式的區域性變數

這種情況我們可以採用模版的引數推導,例如:

template <class T> void func(T iter)

如果T是某個指向特定物件的指標,那麼在func中需要指標所指向物件型別的變數的時候,怎麼辦呢?這個還比較容易,模板的引數推導機制可以完成任務,如下:

template <class T, class U>

void func_impl(T t, U u) {

U temp; // OK, we’ve got the type

// The rest work of func…

}

template <class T>

void func(T t) {

func_impl(t, *t); // forward the task to func_impl

}

通過模板的推導機制,我們輕而易舉的或得了指標所指向的物件的型別,但是事情往往不那麼簡單。例如,如果我想把傳遞給func的這個指標引數所指的型別作為返回值,顯然這個方法不能湊效了,這就是我們的case 2

Case2 對於函式的返回值

儘管在func_impl

中我們可以把U作為函式的返回值,但是問題是使用者需要呼叫的是func,於是,你不可能寫出下面的程式碼:

template <class T, class U>

U func_impl(T t, U u) {

U temp; // OK, we’ve got the type

// The rest work of func…

}

template <class T>

(*T) func(T t) { // !!!Wrong code

return func_impl(t, *t); // forward the task to func_impl

}

int i=10;

cout<<func(&i)<<endl; // !!! Can’t pass compile

紅色的部分概念上如此正確,不過所有的編譯器都會讓你失望。這個問題解決起來也不難,只要做一個iterator,然後在定義的時候為其指向的物件型別制定一個別名,就好了,像下面這樣:

template <class T>

struct MyIter {

typedef T value_type; // A nested type declaration, important!!!

T* ptr;

MyIter(T* p = 0) : ptr(p) {}

T& operator*() const { return *ptr; }

};

而後只要需要其指向的物件的型別,只要直接引用就好了,例如:

template <class I>

typename I::value_type func(I iter) { return *iter; }

很漂亮的解決方案,看上去一切都很完美。但是,實際上還是有問題,因為func如果是一個泛型演算法,那麼它也絕對要接受一個原生指標作為迭代器,但是顯然,你無法讓下面的程式碼編譯通過:

int *p = new int(52);

cout<<func(p)<<endl; // !!!Is there a int::value_type?? Wrong Code here

我們的func無法支援原生指標,這顯然是不能接受的。此時,template partial specialization就派上了用場。

Solution template partial specialization是救世主

既然剛才的設計方案仍不完美,那我們就再加一個間接層,把智慧指標和原生指標統統的封裝起來。在討論之前,先要澄清一下template partial specialization的含義。所謂的partial specialization和模板的預設引數是完全不同的兩件事情,前者指的是當引數為某一類特定型別的時候,採用特殊的設計,也就是說是“針對template引數更進一步的條件限制所設計出來的一個特化版本”;而預設引數則是當不提供某些引數的時候,使用的一個預設。

參考:partial specialization的語法

Template <typename T> class C<T*> {} // 為所有型別為T*的引數而準備的特殊版本

好了,下面我們就找一個專職的負責人,用來封裝迭代器封裝的物件型別。首先,把我們剛才的MyIter重新包裝一下:

template <class I>

struct iterator_traits {

Typedef I::value_type value_type;

}

現在,我們的func又有了新的面貌:

template <class I>

typename iterator_traits<I>::value_type func(I ite) {

return *ite;

}

儘管這次我們的函式返回值的長度有些嚇人,但是,我們的確為原生指標找到了好的解決方案。只要為原生指標提供一個偏特化的iterator_traitsOK了。如下:

template <class I>

struct iterator_traits<T*> {

typedef T value_type;

};

下面,我們終於可以讓func同時支援智慧指標和原生指標了:

template <class I>

struct iterator_traits {

Typedef I::value_type value_type;

}

template <class T>

struct iterator_traits<T*> {

typedef T value_type;

};

template <class I>

typename iterator_traits<I>::value_type func(I ite) {

return *ite;

}

int main() {

MyIter<int> iter = new int(520);

int *p = new int(520);

// This time the following two statements will success

cout<<func(iter)<<endl;

cout<<func(p)<<endl;

return 0;

}

但是,我們離最後的成功還有最後一步,如果,我們需要宣告一個value_type型別的左值,但是卻給iterator_traits傳遞了一個const int*,顯然結果有問題,於是,為const T*也另起爐灶,準備一份小炒:

template<class T>

struct iterator_traits<const T*> {

typedef T value_type;

}

OK ,現在萬事大吉,無論是正宗迭代器,原生指標,const原生指標,我們都可以利用iterator_traits萃取出其封裝的物件的型別,萃取技術由此而來。

第二部分 基於泛型的型別萃取技術

總結一下,我們之所以要萃取迭代器相關的型別,無非是要把迭代器相關的型別用於宣告區域性變數、用作函式的返回值等一系列行為。對於原生指標和point-to-const型別的指標,採用模板偏特化技術對其進行特殊處理,另外,對於point-to-const型別的指標,為了保證宣告左值時語義正確,特化時按照普通原生指標處理。

實際上,把我們剛才的例子提煉一下,迭代器相應型別不僅僅有迭代器封裝的物件型別,STL中對這些型別作了整理,有如下幾種:

template <class I>

struct iterator_traits {

typedef typename I::iterator_category iterator_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;

}

當然,也如你所想,對於原生pointerpointer-to-const這兩種情況,STL分別對其進行了特化處理。如果你看了上面的程式碼卻不知所云,也屬正常,在去了解特化版本之前,我們先來看看這五種型別的含義。

Type 1 value_type

這個型別和我們在第一部分談到的vlaue_type的含義是一樣的,不多說了。

Type 2 difference_type

用來表示兩個迭代器之間的最大距離。這個型別用來對某種演算法提供計數功能,例如:

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;

}

也許這個例子最足以說明問題,任何的解釋都沒必要了。這裡需要說明的是。對於原生指標,由於不存在int::difference_type的情況,所以,iterator_traits對其進行特化:

template <class I>

class iterator_traits<I*> {

typedef ptrdiff_t difference_type;

}

這裡,ptrdiff_t是定義在cstddef中的一個C++內建型別,在GNU gcc中,定義如下:

typedef long int ptrdiff_t;

同樣,對於pointer-to-const,也要入法炮製:

template <class I>

class iterator_traits<const I*> {

typedef ptrdiff_t difference_type;

}

再一次,偏特化技術幫了大忙,現在count可以處理所有型別迭代器的difference_type了。

Type 3 reference

這裡,reference type指的是迭代器封裝物件的型別的引用。這個型別的出現主要是為了解決對指標進行解引用的時候,返回什麼樣的物件的問題。我們希望:

MyIter<int> iter(new int(10));

*iter = 52;

Int *p = new int(10);

*p = 52;

是一樣的。於是,reference_type一般用在迭代器的*運算子過載上,讓所有的“指標家族”有同樣的表現形式。於是,如果value_typeT,那麼reference_type就是T&,如果value_typeconst Treference_type就是const T&

Type 4 pointer

C++ 中指標和引用總是有著密切的關係。如果我們想返回迭代器封裝的物件的地址,就需要用到這裡的pointer_type,主要用在迭代器中對->運算子過載的問題。對於一個智慧指標來說,通常我們都需要下面的兩個運算子過載:

T& operator*() const { return *ptr; } // T& is reference type

T* operator->() const { return ptr; } // T* is pointer type

同樣,為了能夠對迭代器和原生指標都能夠在演算法上有統一的表現形式,在iterator_traits中加入了下面的型別

template <class T>

struct iterator_traits {

typedef typename I::pointer pointer;

typedef typename I::reference reference;

}

同樣,對於原生指標和point-to-const型別的指標作了特化:

template<class T>

struct iterator_traits<T*> {

typedef typename T* pointer;

typedef typename T& reference;

}

而這次,對於point-to-const型別的指標,則有些特別:

template<class T>

struct iterator_traits<const T*> {

typedef typename const T* pointer;

typedef typename const T& reference;

}

也就是說,當我們解引用一個封裝了常量物件的迭代器的時候,返回的型別應該是const T&,取一個封裝了常量對物件的迭代器中的元素的地址,返回的應該是const T*。最終的結果,就是所有的演算法都有了一個統一的表達方式:

template <class T>

typename iterator_traits<T>::reference func() {}

template <class T>

typename iterator_traits<T>::pointer func() {}

Type 5 iterator_category

這個型別的作用是按照迭代器的移動特性和能夠在該迭代器上實施的操作對迭代器進行分類,之所以這樣做,完全是為了效率的考量。不過,在我看來,對其分類的因素實際上只有迭代器的移動特性,而分類,也非常簡單:一步步向前挪的型別和一步跨到位的型別。

STL中,共有以下5種迭代器型別:

l 單向移動只讀迭代器 Input Iterator

l 單向移動只寫迭代器 Output Iterator

l 單向移動讀寫迭代器 Forward Iterator

l 雙向移動讀寫迭代器 Bidirectional Iterator

以上4種屬於單步向前挪型的迭代器,還有一種雙向移動讀寫迭代器屬於一步跨到位型:

l 隨機訪問迭代器 Random Access Iterator

按照強化關係,上面5種迭代器的關係如下:

Input Iterator        Output Iterator
     |                      |
     +-----------+----------+
                 |
          Forward Iterator
                 |
       Bidirectional Iterator
                 |
       Random Access Iterator

STL的各種演算法中,遍歷元素是很常用的,於是我們就用advance()這個函式作個例子,看看每個迭代器的型別,這個函式負責把迭代器移動特定的長度:

// The input iterator version, an O(N) algorithm

template <class InputIterator, class Distance>

void Advance_II(InputIteraotr& i, Distance n) {

while(n--) i++; // This is step by step moving

}

其實,OutputForward型別的迭代器在移動上和Input型別是一樣的。不再熬述,來看看Bidirectional型別:

// The bidirectional iterator version, an O(N) algorithm

template <class BidirectionalIterator, class Distance>

void Advance_BI(BidirectionalIterator& i, Distance n) {

if(n >= 0)

while(n--) i++;

else

while(n++) i++;

}

加入了雙向移動,但仍然要單步進行。最後,看看隨機訪問型別:

// The random access version, an O(1) algorithm

template <class RandomAccessIterator, class Distance>

void Advance_RAI(RandomAccessIterator& i, Distance n) {

i += n;

}

最後,我們可以構想一個把這3個函式封裝起來的函式advance,專門負責迭代器的移動。

template <class InputIterator, class Distance>

void advance(InputIterator& I, Distance n) {

if(is_ramdom_access_iterator(i)) // How to judge?

advance_RAI(I, i);

相關推薦

C++技術

自從C++中引入了template後,以泛型技術為中心的設計得到了長足的進步。STL就是這個階段傑出的產物。STL的目標就是要把資料和演算法分開,分別對其進行設計,之後通過一種名為iterator的東西,把這二者再粘接到一起。設計模式中,關於iterator的描述為:一種能夠

C++traits(技術

traits相關總結: 1.typedef 可以在class或者struct中定義 template class CXX {        typedef T value_type; }; 同樣,template可以嵌入template! 2.見到template<

C++ 模板型別技術 traits

當函式,類或者一些封裝的通用演算法中的某些部分會因為資料型別不同而導致處理或邏輯不同(而我們又不希望因為資料型別的差異而修改演算法本身的封裝時),traits會是一種很好的解決方案。(型別測試發生在編譯期) 自從C++中引入了template後,以泛型技術為中心的設計得到了長足的進步。STL就是

c++模板

假如,我們要設計一個_Copy的模板函式。我們為了提高效率採用memcpy,可以這樣寫: template<typename T> T* _Copy(T* dest, T* src, size_t n) { memcpy(des

C# Math整數

C# 之 Math取整數 引用 主要用到 System 名稱空間下的一個數據類 Math ,呼叫他的方法。 C#取整函式使用詳解: 1、Math.Round是"就近舍入",當要舍入的是5時與"四捨五入"不同(

C++型別技巧

在我們前面寫順序表的時候會產生一個問題。 使用型別萃取的原因 就是當你的順序表是自定義型別,我們進行順序表增容的時候,這個時候會出現一個問題,比如string型別,這個型別中有一個_buf與_ptr,當儲存少於16個的時候這時會儲存在_buf當中的,如果

STL原始碼分析traits

前言 前面我們分析了迭代器的五類, 而迭代器所指向物件的型別被稱為value type. 傳入引數的型別可以通過編譯器自行推斷出來, 但是如果是函式的返回值的話, 就無法通過value type讓編譯器自行推斷出來了. 而traits就解決了函式返回值型別. 同樣原生指標不能內嵌型別

C# 分數的 加 減 乘 除 餘數

定義分數(Fraction)類:  1)、成員變數 私有欄位以及可讀可寫屬性:分子、分母 2)、成員方法: * 列印分數資訊(例如: 1 / 3)   PrintFraction() * 約分 Reduction() * 建立一個方法交換分子和分母的值 Exchang

C++】類模板的特化及型別

關於C++模板的初階學習(函式模板和類模板)總結於我的另一篇部落格: https://blog.csdn.net/hansionz/article/details/83827329 類模板的特化及型別萃取 1.非型別模板引數 2.模板的特化 2

模板型別

函式類模板萃取主要針對的是含有自定義型別的函式 ***我們的型別函式如果需要拷貝往往可以通過給定庫函式經行萃取,但是尼?***我們的型別函式型別中每一個變數中含有的成員個數都是不知道的,我們就需要另外一種拷貝深拷貝的方式,對我們的自定義型別經行處理。 好了,我們定義一種不需要傳遞第三引數的方

C++中的原理

 萃取(traits)     它主要解決的問題是相應型別(associated type),迭代器所指之物的型別便是其一。若要以“迭代器所指物件的型別”來宣告一個物件,如果如果沒有traits ,解決辦法是:利用function template的引數推導(argumen

c++:模板的型別

首先我們通過一個模板類的順序表,來了解c++在什麼情況下需要對不同的型別不同對待。 這裡為了說明重點,只寫了增容和尾插部分。 template <class T> class SeqList { public: SeqList()

C++中的特化問題和型別問題

模板的特化 概念  從字面上來解釋,就是為已有的模板引數進行一些使其特殊化的指定,使得以前不受任何約束的模板引數,或受到特定的修飾(例如const或者搖身一變成為了指標,甚至是經過別的模板類包裝之後的模板型別)或完全被指定了下來。 全特化

c++::關於型別

聽到型別萃取這個名字,很多人肯定都感覺好高大上啊,會不會很難,是個什麼東西呢?彆著急,聽我娓娓道來。 【型別萃取】說白了就是對模板特化的一種應用。 1、型別萃取的作用:型別萃取使用模板技術來萃取型別(

STL原始碼解析uninitialized_fill_n簡單測試-(用到了迭代器和型別

1. 《STL原始碼解析》P72的uninitialized_fill_n函式; #include <iostream> #include <new.h> //using namespace std; //不要std,因為std名稱空間中也有inp

C++中的機制(traits)

由來 在設計迭代器(iterator)時,考慮到需要經常訪問迭代器所指物件之型別,稱之為該迭代器的value_type,利用內嵌型別宣告typedef可輕鬆實現隱藏所指物件型別: template&

C++程式碼片段(一)函式返回值型別,引數型別,引數個數

函式的型別主要集中在以下幾種 函式指標 函式物件,是一個類物件,內部過載的operator()函式是一個函式指標 lambda,匿名函式物件,同函式物件 function物件 後三者都是類物件,可以看成一種型別 定義基礎模板類 t

yum安裝-安裝mysql--技術支持TPshop商城

mbo 端口 pts 啟動項 下載 依賴 所有者 環境 解決 ## 源碼 編譯安裝 Mysql 以 mysql-5.7.15.tar.gz 為例 安裝中涉及的幾點需要提前說明的問題: 所有下載的文件將保存在 /root 目錄下 mysql 將以

C# 集合ArrayList

img 必須 () pac range tro 我們 list() 叠代 .NET Framework提供了用於數據存儲和檢索的專用類,這些類統稱集合。這些類提供對堆棧、隊列、列表和哈希表的支持。大多數集合類實現系統的接口。以下我們主要來講一下ArrayList。

C#自定義特性

創建 tip comm 字段 運算符 包含 自動 名稱 程序   在前面介紹的代碼中有使用特性,這些特性都是Microsoft定義好的,作為.NET Framework類庫的一部分,許多特性都得到了C#編譯器的支持。   .NET Frmework也允許定義自己的特性。自