C++ STL之Traits

iterator模式定義如下:提供一種方法茧彤,使之能夠依序?qū)ぴL某個聚合物所含的各個元素椅寺,而又無需暴露該聚合物的內(nèi)部表達形式

其中施籍,c++里面各個容器的iterator扮演著將數(shù)據(jù)容器與算法結(jié)合起來的重要角色

將范型算法(find, count, find_if)用于某個容器中,最重要的是要給算法提供一個訪問容器元素的工具,iterator就扮演著這個重要的角色

我們在算法中可能會定義簡單的中間變量或者設(shè)定算法的返回變量類型不铆,這時候需要知道迭代器所指元素的類型是什么,但是由于沒有typeof這類判斷類型的函數(shù),我們無法直接獲取裹唆,那該如何是好誓斥?

不要急,那首先先介紹一下iterator_tarit

iterator_trait

template<class _Tp>
struct iterator_traits<_Tp*>
{
    typedef ptrdiff_t difference_type;
    typedef typename _Tp::value_type value_type;
    typedef typename _Tp::pointer pointer;
    typedef typename _Tp::reference reference;
    typedef typename _Tp::iterator_category iterator_category;
};

看到這個奇奇怪怪的東西许帐,是不是感覺沒什么用劳坑,嗯,沒關(guān)系成畦,先記著

下面距芬,將接著之前的話題涝开,來看看如何提取出iterator所指向的元素類型

value_type

例如

使用typedef

我們可以在迭代器中添加元素的類型

template <class T>
struct MyIter {
    typedef T value_type;
    T * ptr;
    MyIter(T * p = 0) : ptr (p) {};
    T& operator* () const { return *ptr;}
};

template <class I>
typename I::value_type //取出迭代器類中的類型
//用以設(shè)定返回變量類型,但是如果I是指針就會錯誤
get (I ite) {
    return *ite;
}

但是框仔,這個版本并不支持原生指針舀武,然而就迭代器的行為而言,就是面向容器的指針离斩,而正常的STL算法也是支持原生指針的银舱,就如同下面的find一樣

指針和迭代器的作用無非就是為stl算法提供了一個運算范圍以及對容器(無論是vector,list捐腿,亦或是array)的訪問

int main() {
    int a[5] = {1,2,2,2,2};
    int *begin = a;
    int *end = a+5;
    int count = std::count(begin, end, 2); //ok!
    return 0;
}

所以對于第一個版本,我們還要對指針類型進行模版偏特化

提取以及偏特化

前面也提到了柿顶,如果直接使用typename I::value_type茄袖,算法就無法接收原生指針,因為原生指針根本就沒有value_type這個內(nèi)嵌類型

因此嘁锯,我們還需要加入一個中間層對其進行判斷宪祥,看它是不是原生指針,注意家乘,這就是traits技法的妙處所在

如果我們只使用上面的做法蝗羊,也就是內(nèi)嵌value_type,那么對于沒有value_type的指針仁锯,我們只能對其進行偏特化耀找,這種偏特化是針對可調(diào)用函數(shù)get的偏特化,假如get有100行代碼业崖,那么就會造成極大的視覺污染

#include <iostream>
template <class T>
struct MyIter {
    typedef T value_type;
    T * ptr;
    MyIter(T * p = 0) : ptr (p) {};
    T& operator* () const { return *ptr;}
};

template <class I>
typename I::value_type //取出迭代器類中的類型
get (I ite) {
    std::cout << "class version" << std::endl;
    return *ite;
}

template <class I>
I get(I* ite) {
    std::cout << "pointer version" << std::endl;
    return *ite;
}

template <class I>
I get(const I* ite) {
    std::cout << "const pointer version" << std::endl;
    return *ite;
}

int main() {
    int i = 3;
    const int k = 3;
    MyIter<int> v(&i);
    std::cout << get(v) << std::endl;
    std::cout << get(&i) << std::endl;
    std::cout << get(&k) << std::endl;
    return 0;
}

就如同上面這個形式野芒,設(shè)想往get中填充100行代碼,簡直不忍直視双炕,你再看看下面這個狞悲,簡直優(yōu)雅!

利用一個中間層iterator_traits固定了get的形式妇斤,使得重復(fù)的代碼大量減少摇锋,唯一要做的就是稍稍特化一下iterator_tartis使其支持pointerconst pointer:)

#include <iostream>

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

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

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

template <class T>
struct MyIter {
    typedef T value_type;
    T * ptr;
    MyIter(T * p = 0) : ptr (p) {};
    T& operator* () const { return *ptr;}
};

template <class I>
typename iterator_traits<I>::value_type
get (I ite) {
    std::cout << "normal version" << std::endl;
    return *ite;
}

int main() {
    int i = 3;
    const int k = 3;
    MyIter<int> v(&i);
    std::cout << get(v) << std::endl;
    std::cout << get(&i) << std::endl;
    std::cout << get(&k) << std::endl;
    return 0;
}

通過定義內(nèi)嵌類型,我們獲得了知曉iterator所指元素類型的方法站超,通過traits技法荸恕,我們將函數(shù)模板對于原生指針和自定義iterator的定義都統(tǒng)一起來

這就是traits技法的妙處所在

difference type

difference type用于表示兩個迭代器之間的距離的一個類型,也可以用來表示一個容器的最大的容量,因為對于連續(xù)空間的容器死相,頭尾之間的距離就是最大容量

例如count()就必須返回的類型就是迭代器的difference type

對于STL容器類型戚炫,以及原生指針,traits有如下兩個不同版本

template<class I>
struct iterator_traits {
...
    typedef typename I::difference_type difference_type;
}

//原生指針
template<class I>
struct iterator_traits<T*> {
...
    typedef ptrdiff_t difference_type;
}

template<class I>
struct iterator_traits<const T*> {
...
    typedef ptrdiff_t difference_type;
}

reference type

標(biāo)示了引用類型

pointer

標(biāo)示了指針類型

測試

以上說明了迭代器內(nèi)部的幾種重要類型

下面對其進行一個測試媳纬,以此產(chǎn)生一個更直觀的印象

#include <iostream>
#include <vector>

#define Test(x,z,y) std::cout<<std::is_same<std::iterator_traits<x>::z,y>::value<<std::endl

int main() {
#define IVec std::vector<int>::iterator
    Test(IVec,value_type,int); //true
    Test(IVec,difference_type,ptrdiff_t); //true
    Test(IVec,reference,int&); //true
    Test(IVec,pointer,int*); //true

    return 0;
}

從上面可以看出双肤,一個vector<int>::iterator

  • value_type=int
  • difference_type=ptrdiff_t
  • reference=int&
  • pointer=int*

總結(jié)

要牢記iterator是為了訪問容器內(nèi)的元素而存在的施掏,而它內(nèi)置的類型就是范型算法與容器進行溝通的重要工具

而我們使用traits技法主要是為了解決原生指針和自定義iterator之間的不同所造成的代碼冗余

type traits

type traits的出現(xiàn)和STL對于性能的要求有著千絲萬縷的聯(lián)系

試想,對于vector這種大塊分配內(nèi)存茅糜,然后大塊析構(gòu)的容器七芭,如果容器里面是POD的話,那么只要等它的生命周期結(jié)束就行了蔑赘,如果是非POD的話狸驳,那么就要判斷是否擁有no-traits的析構(gòu)函數(shù)

如果是這樣的話,又回到了之前value_type的窘境缩赛,因此耙箍,我們只需要使用type_traits,對POD進行偏特化酥馍,通過兩個神奇的類型進行判斷

struct _true_type{};//無意義的析構(gòu)函數(shù)  
struct _false_type{};//有意義的析構(gòu)函數(shù)  

這樣子就可以讓負責(zé)析構(gòu)的模塊進行判斷了

具體的type_traits如下所示

template<typename T>  
struct type_traits  
{  
  
    typedef _false_type has_trivial_default_constructor;//默認構(gòu)造函數(shù)是否有意義辩昆?  
    typedef _false_type has_trivial_copy_constructor;//拷貝構(gòu)造函數(shù)是否有意義?  
    typedef _false_type has_trivial_assgignment_constructor;//拷貝賦值操作是否有意義?  
    typedef _false_type has_trivial_destructor;//析構(gòu)函數(shù)是否有意義?  
    /*POD意指Plain Old Data,也就是標(biāo)量型別或傳統(tǒng)的C struct(傳統(tǒng)的C struct只能 
    包含數(shù)據(jù)成員,不能包含函數(shù)成員旨袒。也就是所謂的聚合類汁针。POD型別必然包含無意義 
    的ctor/dtor/copy/assignment函數(shù)。 
    */  
    typedef _false_type is_POD_type;//是否為Plain Old Data?  
};  

總結(jié)

通過對type_traits進行特化砚尽,標(biāo)注自己類中的構(gòu)造施无,拷貝等行為是否是有意義的,可以大大提高適配算法的效率必孤,這也是type traits存在的意義

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猾骡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子敷搪,更是在濱河造成了極大的恐慌卓练,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件购啄,死亡現(xiàn)場離奇詭異襟企,居然都是意外死亡,警方通過查閱死者的電腦和手機狮含,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門顽悼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人几迄,你說我怎么就攤上這事蔚龙。” “怎么了映胁?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵木羹,是天一觀的道長。 經(jīng)常有香客問我,道長坑填,這世上最難降的妖魔是什么抛人? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮脐瑰,結(jié)果婚禮上妖枚,老公的妹妹穿的比我還像新娘。我一直安慰自己苍在,他們只是感情好绝页,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寂恬,像睡著了一般续誉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上初肉,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天酷鸦,我揣著相機與錄音,去河邊找鬼朴译。 笑死井佑,一個胖子當(dāng)著我的面吹牛属铁,可吹牛的內(nèi)容都是我干的眠寿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼焦蘑,長吁一口氣:“原來是場噩夢啊……” “哼盯拱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起例嘱,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤狡逢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拼卵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奢浑,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年腋腮,在試婚紗的時候發(fā)現(xiàn)自己被綠了雀彼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡即寡,死狀恐怖徊哑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情聪富,我是刑警寧澤莺丑,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站墩蔓,受9級特大地震影響梢莽,放射性物質(zhì)發(fā)生泄漏萧豆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一蟹漓、第九天 我趴在偏房一處隱蔽的房頂上張望炕横。 院中可真熱鬧,春花似錦葡粒、人聲如沸份殿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卿嘲。三九已至,卻和暖如春夫壁,著一層夾襖步出監(jiān)牢的瞬間拾枣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工盒让, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梅肤,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓邑茄,卻偏偏與公主長得像姨蝴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子肺缕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容