(Boolan) C++ STL與泛型編程

關于STL的話題已經(jīng)探討了好幾個篇了磷蛹,今天來討論一下剩下的一些內(nèi)容吧

一個萬用的hash function

使用以Hash Table為底層的容器,比如unordered_map(hash_map)逛腿,在使用個過程中,需要有計算hash code的方法來計算出元素在hashtable中的具體位置。
那么對于自定義類型來說葵孤,需要指定對應的Hash Function鼻由,只有這樣才能在使用的過程暇榴,系統(tǒng)找到對應元素應該插入的位置,以便系統(tǒng)自動管理我們需要插入到容器中的各個元素蕉世。關于這個函數(shù)該如何定義蔼紧,對于不同個自定義類型來說并不相同,也沒有辦法找到一個通用的方式狠轻,畢竟石頭類和動物類奸例,他們各自的hash function多少會有些區(qū)別吧,具體的定義哈误,還需要依靠開發(fā)這自己了哩至。
那么躏嚎,為什么后還會有關于個萬用的hash function呢?關于這個問題菩貌,先放下不說卢佣,咱們先來看看關于指定hash function的方式吧。

  • 指定自定義的hash function的方法一
#include<functional>
class Customer{
      //........
};
//這是一個自定義的類箭阶,如果使用他作為hashtable為底層的容器時
//系統(tǒng)需要知道如何把他插入到hashtable中的具體位置去


//通過一個類虚茶,重載了操作符()的方式,形成了一個仿函數(shù)(函數(shù)對象)
class CustomerHash
{
public:
      std::size_t operator()(const Customer& c) const{
            return /*........*/;
      }
};

//使用
unordered_set<Customer, CustomerHash> customers;
//聲明容器變量時仇参,指定的第二參數(shù)嘹叫,就是對應的Hash Function
//實際在第二參數(shù)中指定的是一個函數(shù)對象
  • 指定自定義的hash function的方法二
size_t customer_hash_func(const Customer& c)
{
        return /*......*/;
}

//使用
unorder_set<Customer, size(*) (const Cunstomer&)> customers(20, customer_hash_func);
//這個方法實際是通過指定了一個函數(shù)指針的方式來通知容器诈乒,對于這個自定義類到底應該使用哪個hash function
  • 指定自定義的hash function的方法三
//以struct hash 偏特化形式實現(xiàn)hash function
class MyString
{
private:
      char* _data;
      size_t _len;
};

namespace std;
{
      template<>
      struct hash<MyStrinng>
      {
              size_t operatoe()(const MyString& s) const noexcept{
                      return hash<string>()(string(s.get()));
              }
      }
}

那么通過這三種方式可以指定需要的hash function罩扇,但是到底能不能有一個萬能的hash function呢?不論自定義的的class怕磨,內(nèi)涵的成員是由什么組成喂饥,總歸都是有基本數(shù)據(jù)類型所組成(比如int、char肠鲫、double等)员帮,而之前我們也提過,基本數(shù)據(jù)類型导饲,STL是提供了一套Hash Function的實現(xiàn)代碼的捞高。那么,能否依靠之前我們提到的方法將hash code相加來獲取該對象的hash code呢渣锦?(例如下方代碼)

class CustomerHash
{
public:
      std::size_t operator()(const Customer& c) const{
            return std::hash<std::string>()(c.fname) 
                  + std::hash<std::string>()(c.Iname) 
                  + std::hash<long><c.no);
      }
}
//這個方法可以實現(xiàn)出計算Hash code的功能硝岗,但是,不得不提出的就是
//這個方法其實對于hash code的重復概率比較高泡挺,因為只是簡單的相累加嘛辈讶。而重復的hash code 會導致“bucket”中的元素過多,影響查詢的效率

其實在*** C++ TR1 ***的版本后娄猫,STL為我么提供了一套萬用的Hash Function贱除。那么應該如何使用呢?咱們就先來看看吧媳溺。

//使用萬能的Hash Function
class CustomerHash
{
public:
      std::size_t operator()(const Cunstomer& c) const {
            return hash_val(c.fname, c,Iname, c.no);  
            //在此月幌,只需要調(diào)用hash_val函數(shù),并把每個成員的值傳遞給他就行了
      }
}

咱們看過了這個萬用Hash Functon的使用悬蔽,那么他其中的實現(xiàn)原理到底是什么呢扯躺?咱們來看看他的代碼吧。

#include<functional>
template<typename T>
inline void hash_combine(size_t& seed, const T& val){
        seed = std::hash<T>(val) + 0x9e3779b9
              + (seed << 6) + (seed >> 2);
}

template<typename T>
inline void hash_val(size_t& seed, const T& val){
      hash_combine(seed, val);
}

template<typename T, typename... Types>
inline void hash_val(size_t& seed, const T& val, const Type&... args){
      hash_combine(seed, val);
      hash_val(seed, args...);
}

template<typename... Types>
inline size_t hash_val(const Types&... args){
      size_t seed = 0;
      hash_val(seed, args...);
      return seed;
}

上面的代碼其實寫的還是比較巧妙的,并且使用了一個C++的新特性录语,名字是variadic templates倍啥,也就是可以傳入多個模版。他的作用有點類似與可變參數(shù)的傳遞,但是對于可變參數(shù)來說,改變的是參數(shù)的個數(shù)仪壮,他們的類型都是相同的〉鳎可以通過頭文件<stdarg.h>中的va_list中的 一些列函數(shù)來拿出傳入的每個變量。

而對于variadic templates來說江耀,很重要的是剩胁,傳入函數(shù)中的每個參數(shù)都會具備一個模版,也就是說祥国,可能每個參數(shù)的類型都會不同昵观,那么應該處理每個不同的參數(shù)需要一套對應的解決方案

其實上面的代碼就是這套方案的解法,其實對于函數(shù)

template<typename... Types>
inline size_t hash_val(const Types&... args)

來說可以看作為是一個泛化的版本舌稀,因為這里面的模版可以傳入任意數(shù)量的任意模版類型索昂。
而函數(shù)

template<typename T, typename... Types>
inline void hash_val(size_t& seed, const T& val, const Type&... args)

不難看出,他的模版變?yōu)榱?** 1 + n ***的模式也正是由于這樣的模式扩借,他接收的參數(shù)的范圍是比第一個函數(shù)要少的,那么缤至,也就是范圍其實先對減小潮罪,我們可以把它理解為偏特化的一個過程,也就是在泛化中领斥,調(diào)用了偏特化的函數(shù)嫉到,每次調(diào)用其實都會減少模版的數(shù)量。
觀察之前源碼中的四個函數(shù)不難看出月洛,有三個重載何恶,而最后一個hash_val只接收一個模版,那么也就是最后的時候會被調(diào)用嚼黔,也就是细层,通過函數(shù)重載和模版特化的過程來一層一層把模版參數(shù)的數(shù)量解開。
而最后四級都是調(diào)用hash_combine的函數(shù)來計算出hash code唬涧。

tuple

tuple是在C++ 2.0之后引進的一種存放元素的集合疫赎,通過這個集合,可以存放不同類型的數(shù)據(jù)碎节。

  • 用例
#include <iostream>     // std::cout
#include <tuple>        // std::tuple, std::get, std::tie, std::ignore

int main ()
{
  //通過構(gòu)造函數(shù)來創(chuàng)建tuple對象
  std::tuple<int,char> foo (10,'x');

  //通過make_tuple來創(chuàng)建tuple對象捧搞,并且可以實現(xiàn)自動類型推到
  //實際的類型應該為: tuple<string, double, int, char>
  auto bar = std::make_tuple ("test", 3.1, 14, 'y');

  /從tuple中獲取數(shù)據(jù)
  std::get<2>(bar) = 100;                                    // access element

  int myint; char mychar;
  //通過tie函數(shù)來從tuple中一次性獲取所有的數(shù)據(jù)
  std::tie (myint, mychar) = foo;                            // unpack elements
  
  //也可以通過std::ignore跳過部分不需要的元素,只取出需要的元素
  std::tie (std::ignore, std::ignore, myint, mychar) = bar;  // unpack (with ignore)

  mychar = std::get<3>(bar);

  //直接修改其中元素的值
  std::get<0>(foo) = std::get<2>(bar);
  std::get<1>(foo) = mychar;

  std::cout << "foo contains: ";
  std::cout << std::get<0>(foo) << ' ';
  std::cout << std::get<1>(foo) << '\n';

  typedef tuple<int, float, string> TupleType;
  //獲取tuple中的元素個數(shù)
  cout << tuple_size<TupleType>::value << endl;  //3
  //獲取元素類型
  tuple_element<1, TupleType>::type f1 = 1.0;  //float

  return 0;
}
  • tuple的實現(xiàn)原理
    GNU4.8節(jié)選并簡化版本
//只有一個模版參數(shù)的版本的特化版本
template<typename Values> class tuple;
//沒有模版參數(shù)的版本的特化版本
templat<> class tuple<>{};

//每次都會繼承剔除掉第一個模版參數(shù)的class
template<typename Head, typename... Tail>
class tuple<Head, Tail...>: private tuple<Tail...>
{
    typedef tuple<Tail...> inherited;
public:
    tuple();
    tuple(Head v, Tail... vtail):m_head(v), inherited(vtail...){}

    typename Head::type head() {return m_head;}
protected:
    Head m_head;
};
`tuple<int, float, string> t(6, 6.3, "xxxxx")` UML圖

tuple可以自動幫我們將多個參數(shù)實現(xiàn)繼承,通過這樣的繼承方式胎撇,來處理每個元素的不同的類型介粘。每次繼承的都是模版的后半部分,逐漸將第一個模版參數(shù)解析出來晚树。

tuple中的成員函數(shù)
head()可以返回第一個元素的值姻采。
tail()可以返回父類成分起點,返回類型是父類的類型题涨,但實際上是自己對象偎谁,而由于類型指定,實際返回的就是父類對象了纲堵。

TypeTraits

GNU 2.9版本
struct __true_type{};
struct __false_type{};
//泛化
template<class type>
struct __type_traits{
      typedef __true_type this_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;  //POD = Plain Old Data巡雨,代表舊式的class 也就是struct
};

//int的特化
template<> 
struct __type_traits<int>{
      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;
}

//double的特化
template<> 
struct __type_traits<double>{
      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;
}

在GNU2.9版本中席函,其實typeTraits實際是依靠模版的泛化和特化的版本來讓不同的類型進入不同的代碼塊中的铐望。代碼塊中實際也就是一些typedef,這些typedef茂附,實際就標注了正蛙,是否有默認的構(gòu)造函數(shù),拷貝構(gòu)造营曼,賦值構(gòu)造乒验,析構(gòu)函數(shù)等具體類型的情況。這個版本的type traits 來說蒂阱,需要使用者自己特化一個自己類型的type traits 锻全,實際來說實用性不高。

從C++2.0 開始录煤,type traits的復雜程度大幅上升鳄厌。

C++11 之后type_traits所提供的查詢的項目

對于C++11的改版后的最大的便利就是不需要在重新定義特化版本的type_traits,系統(tǒng)都能夠自動得到具體的屬性特征

{
   std::cout << "int: " << std::is_trivially_copy_constructible<int>::value << std::endl;
}
  • type_traits的實現(xiàn)
    • is_void
//去除const
template<typename _Tp>
struct remove_const{
    typedef _Tp type;
};

template<typename _Tp>
struct remove_const<_Tp const>{
    typedef _Tp type;
};

//取出volatile
template<typename _Tp>
struct remove_volotile{
    typedef _Tp type;
};

template<typename _Tp>
struct remove_volotile<_Tp volatile>{
    typedef _Tp type;
};

//去除const和volatile
template<typename _Tp>
struct remove_cv{
    typedef remove_const<typename remove_volatile<_Tp>::type>::type type;
};


template<typename>
struct __is_void_helper: public false_type{};

template<>
struct __is_void_helper<void>:public true_type{};

//is_void
template<typename _Tp>
struct is_void
    :public __is_void_helper<typename remove_cv<_Tp>::type>::type {}

關于去除const和volatile都是通過模版的泛化和特化組合來實現(xiàn)的
同時妈踊,是否是void的類型了嚎,也是通過模版的泛化和特化的方式來區(qū)分出來不同的類型

  • is_integral
typename<typename>
struct __is_integral_helper
  :public false_type{};

typename<>
struct __is_integral_helper<bool>
  :public true_type{};

typename<>
struct __is_integral_helper<char>
  :public true_type{};

typename<>
struct __is_integral_helper<signed char>
  :public true_type{};

typename<>
struct __is_integral_helper<unsigned char>
  :public true_type{};

template<>
struct __is_integral_helper<int>
  :public true_type{};

template<>
struct __is_integral_helper<unsigned int>
  :public true_type{};

template<>
struct __is_integral_helper<long>
  :public true_type{};

template<>
struct __is_integral_helper<unsigned long>
  :public true_type{};

template<>
struct __is_integral_helper<long long>
  :public true_type{};

template<>
struct __is_integral_helper<unsigned long long>
  :public true_type{};

//is_integral
template<typename _Tp>
struct __is_integral
  :public __is_integral_helper<typename remove_cv<_Tp>::type>::type{};
  • is_class, is_union, is_enum, is_pod
//is_enum
template<typename _Tp>
struct is_enum
    :public integral_constant<bool, __is_enum(_Tp)>{};

//is_union
template<typename _Tp>
struct is_union
    :public integral_constant<bool, __is_union(_Tp)>{};

//is_class
template<typename _Tp>
struct is_class
    :public integral_constant<bool, __is_class(_Tp)>{};

//is_pod
//Could use is_standard_layout && is_trivial instead of the builtin.
template<typename _Tp>
struct is_pod
    :public integral_constant<bool, __is_pod(_Tp)>();

在標準庫中,沒有__is_xxx(_Tp)的內(nèi)容廊营,實際實現(xiàn)應該是調(diào)用了編譯器的底層的函數(shù)

  • is_move_assignable
template<typename _Tp, bool = __is_referenceable<_Tp>::value>
struct __is_move_assignable_impl;

template<typename _Tp>
struct __is_move_assignable_impl<_Tp, false>:public false_type{};

template<typename _Tp>
struct __is_move_assignable_impl<_Tp, true>:public is_assignable<_Tp&, _Tp&&>{};

template<typename _Tp>
struct __is_referenceable:public __or_<is_object<_Tp>, is_reference<_Tp>>::type

template<typename _Res, typename... _Args>
struct __is_referenceable<_Res(_Args...)>:public true_type{};

template<typename _Res, typename... _Args>
struct __is_referenceable<_Res(_Args......)>:public true_type{};

//is_move_assignable
template<typename _Tp>
struct is_move_assignable:public __is_move_assignable_impl<_Tp>{};

cout

class ostream: virual public ios
{
public:
    ostream& operator << (char c);
    ostream& operator << (unsigned char c){return (*this) << (char)c; };
   ostream& operator << (signed char c){return (*this) << (char)c; };
//............................
//cout根據(jù)操作符重載的方式來使得cout可以接受許多的類型
} ;

class _IO_ostream_withassign: public ostream{
public:
    _IO_ostream_withassign& operator= (ostream&);
    _IO_ostream_withassign& operator=(_IO_ostream_withassign& rhs){
        return oprator=(static_cast<ostream&> (rhs); 
    }
}

extern _IO_ostream_withassign cout;

moveable元素對于容器影響

moveable是C++11之后推出的功能

  • 對vector的影響



    對于插入300萬個數(shù)據(jù)歪泳,vector會牽扯到多次擴容,所以在搬動數(shù)據(jù)的過程中赘风,需要調(diào)用copy Constructer或move Constructer夹囚。
    有圖上可以知道,有move Constructer的結(jié)果會更快邀窃。

  • 對list的影響


對于沒有太多將數(shù)據(jù)搬移的list來說荸哟,有沒有move Constructer的效率差別不大

  • 定義一個moveable class
class MyString{
public:
    static size_t DCtor;  //累計調(diào)用默認ctor的次數(shù)
    static size_t Ctor;  //累計ctor的調(diào)用次數(shù)
    static size_t CCtor;  //累計copy-ctor的調(diào)用次數(shù)
    static size_t CAsgn;  //累計copy-asgn的調(diào)用次數(shù)
    static size_t MCtor;  //累計move-ctor的調(diào)用次數(shù)
    static size_t MAsgn;  //累計move-asgn的調(diào)用次數(shù)
    static size_t Dtor; //累計dtor的調(diào)用次數(shù)

private:
    char* _data;
    size_t _len;
    void _init_data(const char* s){
        _data  = new char[_len + 1];
        memcpy(_data, s, _len);
        _data[_len] = '0';
    }
public:
    //default ctor;
    MyString():_data(NULL), _len(0){ ++DCtor; }
    //ctor
    MyString(const char* p): _len(strlen(p)){
        ++Ctor;
        _init_data(p);
    }

    //copy ctor
    MyString(const  MyString& str): _len(str._len){
        ++CCtor;
        _init_data(str._data);
    }

    //move ctor, with noexcept
    MyString(MyString&& str) noexcept
        :_data(str._data), _len(str._len){
        ++MCtor;
        str._len = 0;
        str._data = NULL;  //避免delete(in dtor)
    }

    //cope assignment 
    MyString& operator= (const MyString& str){
        ++CAsgn;
        if(this != &str){
            if(_data) delete _data;
            _len = str._len;
            _init_data(str._data);
        }
        reutrn *this;
    }
    
    //move assignment
    MyString& operator=(MyString&& str) noexcept{
        ++MAsgn;
        if(this != &str){
            if(_data) delete _data;
            _len = str._len;
            _data = str._data;  //move
            str._len = 0;
            str._data = NULL;
        }
        return *this;
    }

    //dtor
    vitrual ~MyString(){
        ++Dtor;
        if(_data) delete _data;
    }

    bool operator< (const MyString& rhs) const 
    {
        return std::string(this->_data) < std::string(rhs._data);
    }

    bool operator==(const MyString& rhs) const 
    {
        return std::string(this->_data) == std::string(rhs._data);
    }
    char* get() const {return _data;}
};

size_t MyString::DCtor = 0; 
size_t MyString::Ctor = 0;
size_t MyString::CCtor = 0;
size_t MyString::CAsgn = 0;
size_t MyString::MCtor = 0; 
size_t MyString::MAsgn = 0;
size_t MyString::Dtor = 0; 

namespace std{
    template<>
    struct hash<MyString>{
        size_t operator()(const MyString& s)const noexcept{ return hash<string>()(string(s.get()));}
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末假哎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鞍历,更是在濱河造成了極大的恐慌舵抹,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劣砍,死亡現(xiàn)場離奇詭異惧蛹,居然都是意外死亡,警方通過查閱死者的電腦和手機刑枝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門香嗓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人装畅,你說我怎么就攤上這事靠娱。” “怎么了掠兄?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵像云,是天一觀的道長。 經(jīng)常有香客問我蚂夕,道長迅诬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任婿牍,我火速辦了婚禮侈贷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘等脂。我一直安慰自己铐维,他們只是感情好,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布慎菲。 她就那樣靜靜地躺著,像睡著了一般锨并。 火紅的嫁衣襯著肌膚如雪露该。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天第煮,我揣著相機與錄音解幼,去河邊找鬼。 笑死包警,一個胖子當著我的面吹牛撵摆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播害晦,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼特铝,長吁一口氣:“原來是場噩夢啊……” “哼暑中!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鲫剿,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤鳄逾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后灵莲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雕凹,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年政冻,在試婚紗的時候發(fā)現(xiàn)自己被綠了枚抵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡明场,死狀恐怖汽摹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榕堰,我是刑警寧澤竖慧,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站逆屡,受9級特大地震影響圾旨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜魏蔗,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一砍的、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧莺治,春花似錦廓鞠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至榄审,卻和暖如春砌们,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搁进。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工浪感, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饼问。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓影兽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親莱革。 傳聞我的和親對象是個殘疾皇子峻堰,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

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