VS2015中STL源码解析1(霜之小刀)

来源:互联网 发布:网络借贷备案信息披露 编辑:程序博客网 时间:2024/04/30 13:54

VS2015STL源码解析1(霜之小刀)

QQ:2279557541

Email:lihn1011@163.com

VS2015STL源码解析1(霜之小刀) 1

1. 声明 2

2. 该期主要内容概述 2

3. Array-固定大小的数组容器 2

3.1. 类介绍 2

3.2. Array的函数以及使用介绍 3

3.2.1.1. void assign(const _Ty& _Value)-设置容器的内容 3

3.2.1.1.1. 说明 3

3.2.1.1.2. 参数 3

3.2.1.1.3. 返回值 3

3.2.1.1.4. 使用示例 3

3.2.1.2. void fill(const _Ty& _Value)-设置容器的内容 4

3.2.1.2.1. 说明 4

3.2.1.2.2. 参数 4

3.2.1.2.3. 返回值 4

3.2.1.2.4. 使用示例 4

3.2.2. 源码解析 5

3.2.2.1. 梗概 5

3.2.2.2. assign 5

3.2.2.3. fill 6

4. type_traits 6

4.1. 头文件介绍 6

4.2. 源码解析 6

4.2.1. 梗概 6

4.2.2. is_pointer 6

4.3. xtr1common 8

4.4. 头文件介绍 8

4.5. 源码解析 8

4.5.1. 梗概 8

4.5.2. is_same 8

4.5.3. remove_cv 9

4.5.4. _Cat_base 10

4.5.5. integral_constant 10

5. xutility 11

5.1. 头文件介绍 11

5.2. 源码解析 11

5.2.1. 梗概 11

5.2.2. _Is_character 11

5.2.3. _Fill_n_unchecked1 12

5.2.4. _Fill_n_unchecked 13

5.2.5. _Fill_memset_is_safe 13

5.2.6. _Fill_memset_is_safe_helper 14

5.2.7. Conjunction 15

5.2.8. disjunction 17

 

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

 

这个与conjuction的实现是在是太相似了,所以这里不打算多讲源码了了,只不过意思不太不太一样,conjuction指的是所有传入模板参数中有一个为false_type即为false_type,而disjuction值得是有一个为true_type即为true_type.

1 0