VS2015中STL原始碼解析1(霜之小刀)
VS2015中STL原始碼解析1(霜之小刀)
QQ:2279557541
Email:[email protected]
1. 宣告
本文參考了大量http://www.cplusplus.com中的內容,在此非常感謝.
部落格分為兩份,一份是按照內容分開,每個標頭檔案一個檔案,持續更新。
位於:稍後更新
另一份是,按照每次閱讀的內容進行分開,循序漸進,每一份為一個節點。
位於:稍後更新
由於部落格中無法自動索引,所以該文件的word版本
位於:http://download.csdn.net/detail/lihn1987/9550806
2. 該期主要內容概述
主要介紹了array中的assign函式和fill函式的介紹以及完整實現。
另外有視訊版本位於:http://i.youku.com/shuangzhixiaodao
3. Array-固定大小的陣列容器
3.1. 類介紹
此類是在c++11之後的版本才新增進來的.
array是固定大小的序列容器:它們在一個嚴格的線性序列中有一個特定的元素數量。
在內部,陣列不儲存任何資料以外的元素(包括它的大小也只是一個模板引數,在編譯的時候就已經確定了)。它可以向普通陣列一樣使用語法([ ])。這個類僅僅是增加了一個層的成員和全域性函式,所以可以作為標準容器使用。
同其他的標準容器相比,array
零大小的array是有效的,但是他不能被使用.
與標準庫中的其他容器不同的是,交換2個array容器是一個線性操作,他會在範圍內分別交換每一個數據,而這通常是一個效率相當低的操作。另一方面,這使得指向容器內元素的迭代器保持著與原來容器聯絡。
陣列容器的另一個獨特的特點是,它可以被視為陣列物件: <array> 頭檔案過載get函式來訪問陣列的元素,tuple_size和tuple_element型別.
3.2. Array的函式以及使用介紹
3.2.1.1. void assign(const _Ty& _Value)-設定容器的內容
3.2.1.1.1. 說明
將容器內所有元素的值設定為填充為引數的內容.
3.2.1.1.2. 引數
_Value:要用於填充所有元素的值.
3.2.1.1.3. 返回值
無.
3.2.1.1.4. 使用示例
#include <iostream>
#include <array>
int main()
{
const int test_array_size = 3;
std::array<int, test_array_size> test_array;
test_array.assign(1);
for (int i = 0; i < test_array_size; i++)
{
std::cout << test_array[i]<<std::endl;
}
system("pause");
return 0;
}
輸出的內容為
1
1
1
3.2.1.2. void fill(const _Ty& _Value)-設定容器的內容
3.2.1.2.1. 說明
將容器內所有元素的值設定為填充為引數的內容.
3.2.1.2.2. 引數
_Value:要用於填充所有元素的值.
3.2.1.2.3. 返回值
無.
3.2.1.2.4. 使用示例
#include <iostream>
#include <array>
int main()
{
const int test_array_size = 3;
std::array<int,test_array_size>test_array;
test_array.fill(1);
for (int i = 0;i <test_array_size;i++)
{
std::cout << test_array[i]<<std::endl;
}
system("pause");
return 0;
}
輸出的內容為
1
1
1
3.2.1. 原始碼解析
3.2.1.1. 梗概
翻開array的標頭檔案,我們暫時先不看其中的函式,則得到如下的程式碼片段.
位於array檔案的19行
template<class _Ty,
size_t _Size>
class array
{
public:
typedef array<_Ty,_Size>_Myt;
typedef _Ty value_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef _Ty *pointer;
typedef const _Ty *const_pointer;
typedef _Ty&reference;
typedef const _Ty&const_reference;
typedef _Array_iterator<_Ty,_Size>iterator;
typedef _Array_const_iterator<_Ty,_Size>const_iterator;
typedef _STD reverse_iterator<iterator>reverse_iterator;
typedef _STD reverse_iterator<const_iterator>const_reverse_iterator;
_Ty _Elems[_Size];
};
從這段程式碼中我們可以看出,這其中包括STL標準中型別的定義,以及array類中成員變數_Elems的定義,這其中可以看出,array類的實現其實就是基於一個_Ty _Elems[_Size]的陣列,_Ty表示陣列的型別,_Size表示陣列的大小,而這兩個引數都是通過模板
template<class _Ty,size_t _Size>
傳遞進來的.
另外這段程式碼中的_STD是被define成了::std::的意思,這裡加_STD的目的其實就是為了使類內的reverse_iterator不要和全域性空間內的reverse_iterator名稱空間相互汙染.
之後我們一個一個的進行閱讀.
3.2.1.2. assign
用於填充array中的值。
位於array檔案第39行
void assign(const _Ty&_Value)
{ // assign value to all elements
_Fill_n_unchecked(_Elems,_Size,_Value);
}
這裡簡單介紹一下,如果_Ty的型別為char, unsigned char, signed char這幾種型別的話,則呼叫memset進行賦值,否則的話,就是用for進行迴圈賦值。
為什麼這樣處理呢?因為memset進行連續記憶體的賦值速度是非常快的,而使用for迴圈的話效率相對低得多。
關於_Fill_n_unchecked的實現方式見_Fill_n_unchecked。
3.2.1.3. fill
位於array檔案第44行
void fill(const _Ty&_Value)
{ // assign value to all elements
_Fill_n_unchecked(_Elems,_Size,_Value);
}
同assign完全一樣這裡就不做多的敘述了。
4. type_traits
4.1. 標頭檔案介紹
4.2. 原始碼解析
4.2.1. 梗概
4.2.2. is_pointer
該模板的主要作用是用來判斷傳入的模板引數是否是指標。(非類的非靜態資料成員指標以及非類的非靜態函式成員指標。)
額。。。。這個意思描述的有些複雜。我們換種方式描述一下
(是指標型別)&&(不是非靜態的資料成員指標)&&(不是非靜態的函式成員指標)。
也許這種描述會好點,也許。。。。。
下面是程式碼解析
位於type_traits的290行
template<class _Ty>
struct _Is_pointer<_Ty *>
: _Cat_base<!is_member_object_pointer<_Ty *>::value
&& !is_member_function_pointer<_Ty *>::value>
{ // determine whether _Ty is a pointer
};
template<class _Ty>
struct is_pointer
: _Is_pointer<typename remove_cv<_Ty>::type>
{ // determine whether _Ty is a pointer
};
從這裡,我麼看出is_pointer這個模板結構體的內容是空的,所有內容都是繼承自_Is_pointer。
然後我們再看傳入_is_pointer的引數remove_cv<_Ty>::type,這裡有用到了remove_cv<_Ty>其實這個remove_cv就是利用模板特化的概念去除_Ty型別可能擁有的const和volatile的修飾符,其具體實現見remove_cv。
然後再看_is_pointer的實現中,它又是基於_Cat_base的,_Cat_base其實就是根據傳入的模板引數是true還是false,決定返回的型別是true_type還是false_type.
然後_Cate_base的引數使用到了is_member_object_pointer與is_member_function_pointer。
其中
is_member_object_pointer是用來判斷傳入的型別是否是類的資料成員指標型別。
is_member_function_pointer是用來判斷傳入型別是否是類的函式成員指標型別。
知道了這些模板的含義,我們再來分析下這個函式的含義是不是同該段開始介紹的一樣了呢?
另外這裡需要科普兩個概念
1、類的資料成員指標型別
寫一段程式碼來示例
class test_class
{
public:
int m_val;
};
int main()
{
int test_class::*test_func = &test_class::m_val;
test_class test_inst;
test_inst.*test_func = 1010;
std::cout<<test_inst.m_val;
system("pause");
return 0;
}
輸出為1010.
這就是定義了一個int test_class::*型別的類的資料成員的指標,將其命名為test_func,定義為指向test_class的m_val。
呼叫的時候就由於這是個指標,因此只需加個*即可呼叫。呼叫test_inst.*test_func的時候就相當於呼叫了test_inst.*m_val。
1、類的函式成員指標
寫一段程式碼來示例
class test_class
{
public:
int m_val;
void func(int param) {m_val =param; };
};
int main()
{
void(test_class::*test_func)(int) = &test_class::func;
test_class test_inst;
(test_inst.*test_func)(1010);
std::cout<<test_inst.m_val;
system("pause");
return 0;
}
輸出為1010.
其使用方式同調用類的資料成員指標的方式基本一致。不過這裡要注意下之所以寫成(test_inst.*test_func)(1010);在前面加了個括號是因為操作符優先順序的問題。
4.3. xtr1common
4.4. 標頭檔案介紹
4.5. 原始碼解析
4.5.1. 梗概
4.5.2. is_same
用於判斷傳入的2個模板引數是否是統一種型別,如果相同則為true_type,如果不相同則為false_type.
原始碼:
位於
template<class _Ty1,
class _Ty2>
struct is_same
: false_type
{ // determine whether _Ty1 and _Ty2 are the same type
};
template<class _Ty1>
struct is_same<_Ty1,_Ty1>
: true_type
{ // determine whether _Ty1 and _Ty2 are the same type
};
這段程式碼非常簡單,就是利用模板的特化,只要傳入的2個引數為同一種類型,則為true_type否則為false_type.
4.5.3. remove_cv
用於移除傳入模板引數的const與volatile的修飾符。
原始碼:
位於xtr1common檔案104行
template<class _Ty>
struct remove_const
{ // remove top level const qualifier
typedef _Ty type;
};
template<class _Ty>
struct remove_const<const _Ty>
{ // remove top level const qualifier
typedef _Ty type;
};
// TEMPLATE CLASS remove_volatile
template<class _Ty>
struct remove_volatile
{ // remove top level volatile qualifier
typedef _Ty type;
};
template<class _Ty>
struct remove_volatile<volatile _Ty>
{ // remove top level volatile qualifier
typedef _Ty type;
};
// TEMPLATE CLASS remove_cv
template<class _Ty>
struct remove_cv
{ // remove top level const and volatile qualifiers
typedef typename remove_const<typename remove_volatile<_Ty>::type>::type type;
};
從原始碼中我們可以看出,remove_cv中的type的型別,是被remove_volatile處理了一次,然後再有remove_const處理了一次的。
我們先看remove_volatile中,他有一個特化,就是struct remove_volatile<volatile _Ty>這個,指的就是,如果傳入的模板引數具有volatile的屬性那麼就在這個模板中處理。但是其結構體內的type型別,而_Ty中的型別就被巧妙的去除了volatile的修飾符。
remove_const也是一樣,利用這種方法巧妙的去除了const這個修飾符。這樣就形成了remove_cv就是去除了volatile然後又去除了const的這樣一種功能。
4.5.4. _Cat_base
位於xtr1common檔案48行
template<bool _Val>
struct _Cat_base
: integral_constant<bool,_Val>
{ // base class for type predicates
};
這裡其實就是通過傳入的bool型別的模板引數為true還是false,_Cat_base則會成為一個true_type或者false_type的型別。其中的integral_constant型別的實現,詳見integral_constant。
4.5.5. integral_constant
位於位於xtr1common檔案20行
template<class _Ty,
_Ty _Val>
struct integral_constant
{ // convenient template for integral constant types
static _CONST_DATA _Ty value =_Val;
typedef _Ty value_type;
typedef integral_constant<_Ty,_Val>type;
_CONST_FUN operator value_type()const _NOEXCEPT
{ // return stored value
return (value);
}
_CONST_FUN value_type operator()()const _NOEXCEPT
{ // return stored value
return (value);
}
};
typedef integral_constant<bool,true>true_type;
typedef integral_constant<bool,false>false_type;
首先看一下integral_constant這個結構體存了1個成員變數value ,兩個成員型別,分別是 成員變數的基礎型別value_type和自己的型別type,然後呢,又過載了value_type的轉換函式,直接返回value的值,然後過載了小括號的符號,也只直接返回了value的值。
最後看他生成了兩個::std::作用於下的型別,true_type,false_type,這其實主要是用來做模板推倒的型別。
5. xutility
5.1. 標頭檔案介紹
xutility的作用是為整個stl提供除了<utility>以外的功能性函式的標頭檔案,但是這裡要說,這裡的函式只是看看就好了,儘量不要使用,因為他不是標準,不具有移植性.所以這裡就不介紹其中函式的使用和介紹了,僅僅介紹原始碼.
5.2. 原始碼解析
5.2.1. 梗概
5.2.2. _Is_character
template<class _Ty>
struct _Is_character
: false_type
{ // by default, not a character type
};
template<>
struct _Is_character<char>
: true_type
{ // chars are characters
};
template<>
struct _Is_character<signed char>
: true_type
{ // signed chars are also characters
};
template<>
struct _Is_character<unsigned char>
: true_type
{ // unsigned chars are also characters
};
這個模板適用於判斷傳入的模板型別是否是char,或者signed char或者是unsigned char型別的,這個也是利用了模板的特化,非常簡單,沒什麼好說的。
5.2.3. _Fill_n_unchecked1
位於xutility檔案第2781行
template<class _OutIt,
class _Diff,
class _Ty>inline
_OutIt _Fill_n_unchecked1(_OutIt _Dest,_Diff _Count,const _Ty&_Val,false_type)
{ // copy _Val _Count times through [_Dest, ...), no special optimization
for (; 0 <_Count; --_Count, (void)++_Dest)
*_Dest =_Val;
return (_Dest);
}
template<class _OutIt,
class _Diff,
class _Ty>inline
_OutIt _Fill_n_unchecked1(_OutIt _Dest,_Diff _Count,const _Ty&_Val,true_type)
{ // copy _Val _Count times through [_Dest, ...), memset optimization
_CSTD memset(_Dest,_Val,_Count);
return (_Dest +_Count);
}
這裡看到,此函式擁有兩個過載,其不同點在於最後一個引數的型別是true_type還是false_type,而這個引數除了被用作過載,沒有任何作用.
另外我們看兩個過載函式的實現區別
使用true_type型別的函式,是使用memset來填充一段記憶體,這種方式的好處是,memset的速度非常快,但是適用範圍很有限,因為只能按照位元組為單位對記憶體進行填充(比如char),但是對於多個位元組的型別(比如int)就無法適用memset了.
使用false_type型別的函式,是使用遍歷的方式來填充一段記憶體,這種方式速度相對就很慢,因為是一個一個進行賦值的,但是相對於true_type的過載,他的適用範圍就很廣了.
而true_type和falst_type其實只是一個輔助型別,詳情請見integral_constant。
另外這段程式碼裡面有些小小的需要提示下的地方.
1、for (; 0 < _Count; --_Count, (void)++_Dest)中的void是什麼鬼?
我是這樣考慮的.
其實--_Count, (void)++_Dest算是一個逗號表示式,而逗號表示式是有特點的,就是都好表示式是由返回值的,比如
int i = 0,j = 0,k=0;
i = (j = 5,k = 8);
這段能帶嗎的執行結果i的值是8,也就是逗號表示式最後一個表示式的值.
這說明什麼,逗號表示式,除了執行完每個語句外,還要返回一個結果,這就需要損耗效率,所以我們發現一點,在這份std程式碼中,只要for語句中使用逗號表示式的,最後一個表示式的前面必然會加一個void以節省效率.
但是我在反編譯的時候會發現
for (i = 0;i < 5; ++i, ++j);
for (i = 0;i < 5; ++i, (void)++j);
這兩段的執行彙編程式碼沒有任何區別,也就是沒有任何的損耗.就想很多人說for裡面的迴圈變數使用++i要比i++的效率高一樣,其實真正編譯後是沒有區別的,只是老編譯器留下的習慣而已.新的編譯器在這些地方應該都是做了優化的.
2、_CSTD 是什麼?
這其實就是個全域性作用域的意思而已,放置命名衝突被define為::
5.2.4. _Fill_n_unchecked
位於xutility檔案第2800行
template<class _OutIt,
class _Diff,
class _Ty>inline
_OutIt _Fill_n_unchecked(_OutIt _Dest,_Diff _Count,const _Ty&_Val)
{ // copy _Val _Count times through [_Dest, ...), choose optimization
// note: This is called directly from elsewhere in the STL
return (_Fill_n_unchecked1(_Dest,_Count,_Val,_Fill_memset_is_safe(_Dest,_Val)));
}
該函式是用於從_Dest地址填充_Count個_Val的值。
這個函式的內容只是呼叫了_Fill_n_unchecked1。這裡簡單介紹下,_Fill_n_unchecked1的作用主要是通過判斷第三個引數是true_type型別還是false_type型別決定是否使用memset進行填充(利用了函式),其具體內容以及實現參照_Fill_n_unchecked1。
而其中的false_type還是true_type其實是一個輔助類。詳情見integral_constant。
而其中傳入的內容到底是true_type還是false_type是由_Fill_memset_is_safe決定的。
5.2.5. _Fill_memset_is_safe
判斷是否是否可以使用memset進行填充記憶體是否安全。
位於xutility檔案第2742行
template<class _FwdIt,
class _Ty>inline
typename _Fill_memset_is_safe_helper<_FwdIt,_Ty>::type
_Fill_memset_is_safe(const _FwdIt&,const _Ty&)
{ // type deduction for _Fill_memset_is_safe_helper
return {};
}
這個函式主要判斷_FwdIt(指標型別)和_Ty(值型別)是否可以使用memset進行填充,其實就是判斷_FwdIt型別是否是char型的指標。其詳細內容參照_Fill_memset_is_safe_helper。
5.2.6. _Fill_memset_is_safe_helper
用於輔助_Fill_memset_is_safe判斷使用memset進行填充記憶體是否安全。
位於xutility檔案第2725行
template<class _FwdIt,
class _Ty>
struct _Fill_memset_is_safe_helper
{ // determines if _FwdIt and _Ty are eligible for memset optimization in fill
typedef typename iterator_traits<_FwdIt>::value_type _Value_type;
typedef typename conjunction<
is_pointer<_FwdIt>,
disjunction<
conjunction<
_Is_character<_Ty>,
_Is_character<_Value_type>>,
conjunction<
is_same<bool,_Ty>,
is_same<bool,_Value_type>>
>>::type type;
};
哦,fuck!怎麼會有這麼多的模板。。。。。
簡單的先理一下簡化一下
template<class _FwdIt,class _Ty>
struct _Fill_memset_is_safe_helper
{ // determines if _FwdIt and _Ty are eligible for memset optimization in fill
typedef typename iterator_traits<_FwdIt>::value_type _Value_type;
typedef typename conjunction<####>::type type;
};
首先呢,乍一看,這就是一個結構體而已。
其中看註釋我們可以發現這個類是用來判斷_FwdIt型別的指標和_Ty型別的值是否能夠使用memset進行填充。
首先說一下iterator_traits<_FwdIt>::value_type _Value_type這個東西,這個東東呢其實涉及到迭代器,但是這一期的程式碼是在太多了,暫時不講,在這裡寫個TODO:等待後面補充上。我先簡單說下,因為這裡傳入的是指標,所以其實iterator_traits<_FwdIt>::value_type的型別就是_FwdIt的型別。
然後再看後面這段長長的神奇的東東。
typedef typename conjunction<
is_pointer<_FwdIt>,
disjunction<
conjunction<
_Is_character<_Ty>,
_Is_character<_Value_type>>,
conjunction<
is_same<bool,_Ty>,
is_same<bool,_Value_type>>
>>::type type;
我們先把所有模板的功能介紹一下:
1、conjunction的功能為如果傳入的模板引數有一個為false_type則返回的值為一個false_type,否則返回的是true_type.詳情參見conjunction。
2、disjunction的功能和conjection正好相反,如果傳入的模板引數中有一個為true_type這返回true_type否則返回false_type.詳情參見disjunction。
3、is_pointer的功能是判斷is_pointer所傳入的引數是否是一個正常指標(非類成員指標)。其具體的原始碼解析見is_pointer。
4、_Is_character 的功能是判斷傳入的引數是否是char,signed char,unsigned char型別。其具體原始碼見_Is_character。
5、is_same 的功能是判斷傳入的兩個模板引數的型別是否相同,如果相同返回true_type,否則返回false_type。其具體原始碼見is_same。
然後知道了這些模板的含義,我們再來看整體的含義。
_FwdIt代表的是memset地址的型別,_Ty指的是值得型別。
當memset的地址型別為指標型別,且memset值的型別為char或者bool則返回true_type,否則返回false_type.
5.2.7. Conjunction
位於xutility檔案第1065行
template<class..._Traits>
struct conjunction
: _Conjunction<_Traits...>::type
{ // If _Traits is empty, true_type
// Otherwise, if any of _Traits are false, the first false trait
// Otherwise, the last trait in _Traits
};
首先我們看conjunction的註釋,裡面說的是,如果傳入的模板引數為空,則返回true_type,否則,只要傳入的模板引數有一個為false則返回第一個為false的模板引數。但這是如何實現的呢?
我們發現conjunction這個模板結構體其實是沒有內容的,完全將所有的引數傳遞給了_Conjunction,而在_Conjunction對模板進行特化,並解析
下面我們看看_Conjunction的程式碼
位於xutility檔案第1046行
template<>
struct _Conjunction<>
{// Implement conjunction for 0 arguments
typedef true_type type;
};
template<class _Trait>
struct _Conjunction<_Trait>
{// Implement conjunction for 1 argument
typedef _Trait type;
};
template<class _Lhs,class... _Traits>
struct _Conjunction<_Lhs, _Traits...>
{// Implement conjunction for N arguments
typedef typename _Choose_conjunction<_Lhs::value, _Lhs, _Traits...>::type type;
};
可以看出_Conjunction分別對傳入引數為0個和一個的進行特化,其中
若引數為0個,則包含一個型別為true_type的成員型別type
若引數為1個,則包含一個型別為這個引數的成員型別type。
若引數為n個,則呼叫_Choose_conjunction求出返回的模板型別,並使用返回型別中的type作為成員型別type的型別;
所以到這裡還沒結束,我們還需要來看看_Choose_conjunction的原始碼。
位於xutility檔案第1031行
template<bool,
class _Lhs,
class... _Traits>
struct _Choose_conjunction
{ // Select _Lhs if it is false
typedef _Lhs type;
};
template<class _Lhs,
class... _Traits>
struct _Choose_conjunction<true, _Lhs, _Traits...>
{ // Select the conjunction of _Traits if _Lhs is true
typedef typename _Conjunction<_Traits...>::type type;
};
首先簡單的介紹下_Choose_conjunction模板結構體的兩個實現,其中第二個模板是第一個模板的特化。
由於_Choose_conjunction傳入的第一個引數為_Lhs::value,而如果_Lhs型別為true_type則true_type::value是true,而如果_Lhs的型別為false_type,由於false_type::value的值為false,所以
如果傳入的第一個引數為false則使用我們的第一個模板進行解析。直接得出成員型別type的型別為false_type。
如果我們傳入的第一個引數為true,則使用第二個模板進行特化解析,得出其成員型別type的型別為_Conjunction<_Traits...>::type。而這是個什麼鬼?怎麼又回到_Conjunction進行解析了???
等等!!!!!
我們發現一個問題,先前傳入_Conjunction中的模板引數的第一個引數_Lhs已經被解析了,現在只留下剩餘的幾個引數傳了回去,這是個遞迴解析!!!直至找到第一個引數為false的時候返回!!!
哦哦,這下終於解釋通了。。。。
5.2.8. disjunction
這個與conju