1. 程式人生 > >VS2015中STL原始碼解析1(霜之小刀)

VS2015中STL原始碼解析1(霜之小刀)

VS2015STL原始碼解析1(霜之小刀)

QQ:2279557541

Email:[email protected]

1. 宣告 2

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

具有固定的大小,而不通過記憶體分配器(allocator)來進行管理.它只是封裝了一個固定大小的元素陣列.因此,它不能進行童泰的擴充套件和收縮改變陣列的容量.

零大小的array是有效的,但是他不能被使用.

與標準庫中的其他容器不同的是,交換2array容器是一個線性操作,他會在範圍內分別交換每一個數據,而這通常是一個效率相當低的操作。另一方面,這使得指向容器內元素的迭代器保持著與原來容器聯絡。

陣列容器的另一個獨特的特點是,它可以被視為陣列物件: <array> 檔案過載get函式來訪問陣列的元素,tuple_sizetuple_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_traits290

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

用於移除傳入模板引數的constvolatile的修飾符。

原始碼:

位於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