STL源碼分析--traits

更多精彩內容,請關注微信公衆號:後端技術小屋編程

traits(譯做萃取)是C++中一種特殊的編程技法,它是模板元編程最直接的用例之一。經過traits,能夠抽取模板入參類型的各類屬性。接下來咱們經過STL中最多見的幾種traits舉例說明。後端

1 __type_traits:trivial斷定

__type_traits用於判斷類型是否爲trival(譯做平凡)。數組

1.1 爲何要斷定trivial類型

若是一個類型是trivial的,則能夠靜態初始化,能夠用memcpy直接複製數據而不是必須用copy構造函數。其生存期始於它的對象存儲被定義,無需等到構造函數完成。在執行ctor, copy, move, assign, ctor時,能夠採用最有效率的方法:即不執行編譯器自動生成的ctor, copy, assign, ctor, 取而代之的是malloc, free, memcpy這類操做。微信

舉個栗子:_Destroy用於銷燬容器迭代器區間[__first, __last)內全部對象。
而其調用鏈以下:函數

_Destroy
  -> __destroy
    -> __destroy_aux

在實現上,源碼分析

  • _Destory首先使用__VALUE_TYPE獲取指向容器中對象的指針類型,並將其實例做爲入參傳入到__destory中。
  • __destory中,使用__type_traits<_Tp>::has_trivial_destructor判斷容器中對象類型是否爲trivial,並調用__destroy_aux
  • __destroy_aux函數根據_Tp是否爲trivial實現了兩個不一樣的版本。在trivial版本中,什麼都不作,由於trivial類型沒有顯式定義析構函數。在非trivial函數中,則必須調用_Tp的析構函數。

咱們注意到,這裏使用__type_traits用於識別_Tp類型是否trivial佈局

template <class _ForwardIterator>
void
__destroy_aux(_ForwardIterator __first, _ForwardIterator __last, __false_type)
{
  for ( ; __first != __last; ++__first)
    destroy(&*__first);
}

template <class _ForwardIterator> 
inline void __destroy_aux(_ForwardIterator, _ForwardIterator, __true_type) {}

template <class _ForwardIterator, class _Tp>
inline void 
__destroy(_ForwardIterator __first, _ForwardIterator __last, _Tp*)
{
  typedef typename __type_traits<_Tp>::has_trivial_destructor
          _Trivial_destructor;
  __destroy_aux(__first, __last, _Trivial_destructor());
}

template <class _ForwardIterator>
inline void _Destroy(_ForwardIterator __first, _ForwardIterator __last) {
  __destroy(__first, __last, __VALUE_TYPE(__first));
}

1.2 trivial類型的定義

也許你的問題來了,什麼樣的類型纔可稱之爲trivial的?this

若是一個類型知足如下條件中的至少一個,則稱其爲非trival;不然稱其爲trival
1. 顯式定義了構造函數(ctor), 複製構造函數(copy), 移動構造函數(move),賦值運算符(assign), 或析構函數(ctor)之中任何一個。
2. 類中有非POD類型成員
3. 有虛函數
4. 有虛基類

到這裏也許你的問題會更多了,到底什麼是POD類型?指針

POD = Plain Old Data 
根據維基百科的定義,POD類型包括標量類型和POD類類型。POD在源代碼兼容於ANSI C時很是重要。POD對象與C語言的對應對象具備共同的一些特性,包括初始化,複製,內存佈局,尋址等。

標量類型包括:
1. 算數類型(整數/浮點/字符/布爾)
2. 枚舉類型
3. 指針類型(空指針/對象指針/函數指針)
4. 指針到成員類型(例如T C::* 指向類C的類型爲T的數據成員的指針)

POD類類型是指聚合類(經過struct/union聚合)或數組,也不具備下述成員:
1. 指針到成員類型的非靜態數據成員
2. 非POD類型的非靜態數據成員
3. 引用類型的非靜態數據成員
4. 顯式定義的拷貝和賦值算子
5. 顯式定義的析構函數
6. 若是是聚合類且含有顯式定義的構造函數,私有/保護的非靜態成員函數,基類,虛函數 
綜上,不符合以上6條的聚合類/數組纔可稱之爲POD類類型。

1.3 trival類型斷定的實現

在實現上,首先定義通用模板類__type_traits。這裏全部的計算都是基於類型的,所以使用__true_type__false_type分別表示邏輯真/假類型。code

從代碼能夠看到,缺省狀況下,_Tp中默認構造函數/複製構造函數/賦值操做符/析構函數都不是trivial的,_Tp也不是POD類型。

template <class _Tp>
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;
};

struct __true_type {
};

struct __false_type {
};

其次,對於全部標量類型,定義特化模板類__type_traits,由於標量類型,沒有定義默認構造/複製構造/複製操做符/析構函數,標量類型也屬於POD類型。標量類型包含:

bool
char
signed char
unsigned char
wchar_t
short
unsigned short
int
unsigned int
long
unsigned long
unsigned long long
float
double
long double
Tp*
char*
signed char*
unsigned char*,
const char*
const signed char*
const unsigned char*

2 _Is_integer: 整型斷定

_Is_integer用於判斷類型是否爲整數類型

2.1 爲何要進行整型斷定

舉個例子:若是vector中元素類型_Tp是整數類型。在這種狀況下,若是不分區整數類型,那麼編譯器便沒法區分vector<int> a(10, 1);該使用如下代碼中第一種構造函數仍是第二種,由於_InputIterator只是一個模板參數,它能夠是真正的迭代器類型,也能夠是其餘任何一種類型。

爲了避免使編譯器犯難,咱們須要在編譯期間決定_InputIterator是否爲整數類型。這裏用到了_Is_integer,它斷定_InputIterator類型,並返回__true_type__false_type。對應的,_M_initialize_aux針對_InputIterator是否爲整形也實現了兩個版本。最終使得vector構造在兩種不一樣狀況下,保持了各自的語義:

  • 語義一:指定元素數量和初始值,構造vector
  • 語義二:指定起始iterator和終止iterator, 構造vector
// 構造函數1
  vector(size_type __n, const _Tp& __value,
         const allocator_type& __a = allocator_type()) 
    : _Base(__n, __a)
    { _M_finish = uninitialized_fill_n(_M_start, __n, __value); }
    
  // 構造函數2
  // Check whether it's an integral type.  If so, it's not an iterator.
  template <class _InputIterator>
  vector(_InputIterator __first, _InputIterator __last,
         const allocator_type& __a = allocator_type()) : _Base(__a) {
    typedef typename _Is_integer<_InputIterator>::_Integral _Integral;
    _M_initialize_aux(__first, __last, _Integral());
  }

2.2 整型斷定的實現

首先,定義通用模板函數_Is_integer。缺省狀況下全部的類型都不是整型。

template <class _Tp> struct _Is_integer {
  typedef __false_type _Integral;
};

其次,定義特化模板函數_Is_integer,對於如下類型,_Integral__true_type

bool
char
signed char
unsigned char
wchar_t
short
unsigned short
int
unsigned int
long
unsigned long
long long
unsigned long long

PS: STL中遠遠不止上述兩種traits,例如還有__char_traits(見STL源碼分析--string)、iterator_traits(見STL源碼分析--iterator)、_Alloc_traits(見STL源碼分析--內存分配器),在此留給讀者自行分析


更多精彩內容,請關注微信公衆號: 悟空者說


推薦閱讀

更多精彩內容,請掃碼關注微信公衆號:後端技術小屋。若是以爲文章對你有幫助的話,請多多分享、轉發、在看。
二維碼

相關文章
相關標籤/搜索