VS2015中STL源码解析1(霜之小刀)
QQ:2279557541
Email:[email protected]
VS2015中STL源码解析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
本文参考了大量http://www.cplusplus.com中的内容,在此非常感谢.
博客分为两份,一份是按照内容分开,每个头文件一个文件,持续更新。
位于:稍后更新
另一份是,按照每次阅读的内容进行分开,循序渐进,每一份为一个节点。
位于:稍后更新
由于博客中无法自动索引,所以该文档的word版本
位于:http://download.csdn.net/detail/lihn1987/9550806
主要介绍了array中的assign函数和fill函数的介绍以及完整实现。
另外有视频版本位于:http://i.youku.com/shuangzhixiaodao
此类是在c++11之后的版本才添加进来的.
array是固定大小的序列容器:它们在一个严格的线性序列中有一个特定的元素数量。
在内部,数组不保存任何数据以外的元素(包括它的大小也只是一个模板参数,在编译的时候就已经确定了)。它可以向普通数组一样使用语法([ ])。这个类仅仅是增加了一个层的成员和全局函数,所以可以作为标准容器使用。
同其他的标准容器相比,array具有固定的大小,而不通过内存分配器(allocator)来进行管理.它只是封装了一个固定大小的元素数组.因此,它不能进行童泰的扩展和收缩改变数组的容量.
零大小的array是有效的,但是他不能被使用.
与标准库中的其他容器不同的是,交换2个array容器是一个线性操作,他会在范围内分别交换每一个数据,而这通常是一个效率相当低的操作。另一方面,这使得指向容器内元素的迭代器保持着与原来容器联系。
数组容器的另一个独特的特点是,它可以被视为数组对象: <array> 头文件重载get函数来访问数组的元素,tuple_size和tuple_element类型.
将容器内所有元素的值设置为填充为参数的内容.
_Value:要用于填充所有元素的值.
无.
#include
#include
int main()
{
const int test_array_size = 3;
std::array test_array;
test_array.assign(1);
for (int i = 0; i < test_array_size; i++)
{
std::cout << test_array[i]<
输出的内容为
1
1
1
将容器内所有元素的值设置为填充为参数的内容.
_Value:要用于填充所有元素的值.
无.
#include
#include
int main()
{
const int test_array_size = 3;
std::arraytest_array;
test_array.fill(1);
for (int i = 0;i
输出的内容为
1
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命名空间相互污染.
之后我们一个一个的进行阅读.
用于填充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。
位于array文件第44行
void fill(const _Ty&_Value)
{ // assign value to all elements
_Fill_n_unchecked(_Elems,_Size,_Value);
}
同assign完全一样这里就不做多的叙述了。
该模板的主要作用是用来判断传入的模板参数是否是指针。(非类的非静态数据成员指针以及非类的非静态函数成员指针。)
额。。。。这个意思描述的有些复杂。我们换种方式描述一下
(是指针类型)&&(不是非静态的数据成员指针)&&(不是非静态的函数成员指针)。
也许这种描述会好点,也许。。。。。
下面是代码解析
位于type_traits的290行
template<class _Ty>
struct _Is_pointer<_Ty *>
: _Cat_baseis_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);在前面加了个括号是因为操作符优先级的问题。
用于判断传入的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.
用于移除传入模板参数的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
remove_const也是一样,利用这种方法巧妙的去除了const这个修饰符。这样就形成了remove_cv就是去除了volatile然后又去除了const的这样一种功能。
位于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。
位于位于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,这其实主要是用来做模板推倒的类型。
xutility的作用是为整个stl提供除了
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类型的,这个也是利用了模板的特化,非常简单,没什么好说的。
位于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为::
位于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决定的。
判断是否是否可以使用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。
用于辅助_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.
位于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
struct _Conjunction<_Trait>
{// Implement conjunction for 1 argument
typedef _Trait type;
};
template
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的时候返回!!!
哦哦,这下终于解释通了。。。。
这个与conjuction的实现是在是太相似了,所以这里不打算多讲源码了了,只不过意思不太不太一样,conjuction指的是所有传入模板参数中有一个为false_type即为false_type,而disjuction值得是有一个为true_type即为true_type.