实习满2个月了.谨以此文缅怀本来应该是我最后一个暑假却用来实习的暑假.T_T(楼主强烈声明,实习生活很nice,前面只是吐槽,请无视)
入正题,循惯例,上图先:
简单来看,vector是继承_Vector_base,这里可以说分为两层去实现,在_Vector_base当中封装了跟内存分配相关的操作,如_M_allocate(),_M_deallocate()等,在_Vector_base当中有一个内置的结构,叫做_Vector_impl,他包含了3个指针,分别指向内存区域的起始,结束和当前使用到.这个就是vector的实现,所有的操作都是围绕在这3个指针来展开的,简单清晰._M_allocate(),_M_deallocate()其实就是对allocator申请(allocator())和释放(deallocator())接口的封装.
看侯捷兄的源码剖析的时候,最开始讲的就是allocator,SGI的STL使用的allocator是用了内存池的技术,在分配的效率上可能会高一些.在GCC的库实现当中,默认使用的allocator是最最简单的new allocator,不使用带内存池的allocator原因可以在这里找到http://gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt04ch11.html ,GCC库当中其实提供了相当多的allocator,有机会再深入学习一下
pointer的定义如下
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Tp>::other _Tp_alloc_type;
typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer
pointer;
由于vector不是侵入式的容器,所以rebind<_Tp>其实是无关紧要,侵入式的容器主要是讲申请的内存结构跟T的关系,举例来说,vector
与C++11相关的一些改动主要是:
加入move构造函数,针对右值引用的构造优化,如
#ifdef __GXX_EXPERIMENTAL_CXX0X__
_Vector_impl(_Tp_alloc_type&& __a)
: _Tp_alloc_type(std::move(__a)),
_M_start(0), _M_finish(0), _M_end_of_storage(0)
{ }
#endif
#ifdef __GXX_EXPERIMENTAL_CXX0X__
_Vector_base(_Tp_alloc_type&& __a)
: _M_impl(std::move(__a)) { }
_Vector_base(_Vector_base&& __x)
: _M_impl(std::move(__x._M_get_Tp_allocator()))
{ this->_M_impl._M_swap_data(__x._M_impl); }
_Vector_base(_Vector_base&& __x, const allocator_type& __a)
: _M_impl(__a)
{
if (__x.get_allocator() == __a)
this->_M_impl._M_swap_data(__x._M_impl);
else
{
size_t __n = __x._M_impl._M_finish - __x._M_impl._M_start;
_M_create_storage(__n);
}
}
#endif
_M_swap_data()内部实现是利用std::swap交换_Vector_impl的3个指针的值完成move构造
#ifdef __GXX_EXPERIMENTAL_CXX0X__
/**
* @brief Creates a %vector with default constructed elements.
* @param __n The number of elements to initially create.
*
* This constructor fills the %vector with @a __n default
* constructed elements.
*/
explicit
vector(size_type __n)
: _Base(__n)
{ _M_default_initialize(__n); }
/**
* @brief Creates a %vector with copies of an exemplar element.
* @param __n The number of elements to initially create.
* @param __value An element to copy.
* @param __a An allocator.
*
* This constructor fills the %vector with @a __n copies of @a __value.
*/
vector(size_type __n, const value_type& __value,
const allocator_type& __a = allocator_type())
: _Base(__n, __a)
{ _M_fill_initialize(__n, __value); }
#else
/**
* @brief Creates a %vector with copies of an exemplar element.
* @param __n The number of elements to initially create.
* @param __value An element to copy.
* @param __a An allocator.
*
* This constructor fills the %vector with @a __n copies of @a __value.
*/
explicit
vector(size_type __n, const value_type& __value = value_type(),
const allocator_type& __a = allocator_type())
: _Base(__n, __a)
{ _M_fill_initialize(__n, __value); }
#endif
旧的vector(size_type n)的构造函数是利用默认参数进行的,如果没有提供构造的值则使用默认的value_type()来进行构造,而在C++11中将这两者进行了区分(声明了两个函数),内部实现分别调用了_M_default_initialize()和_M_fill_initialize(),应该是处于效率优化而将这两种情况进行分开的.
不仅在构造函数中加入了对右值引用的支持,同样的还有赋值操作符重载里面.C++11带来了初始化列表,同样也在构造函数和赋值重载中有体现,如(没有全部列出)
/**
* @brief Builds a %vector from an initializer list.
* @param __l An initializer_list.
* @param __a An allocator.
*
* Create a %vector consisting of copies of the elements in the
* initializer_list @a __l.
*
* This will call the element type's copy constructor N times
* (where N is @a __l.size()) and do no memory reallocation.
*/
vector(initializer_list __l,
const allocator_type& __a = allocator_type())
: _Base(__a)
{
_M_range_initialize(__l.begin(), __l.end(),
random_access_iterator_tag());
}
/**
* @brief %Vector list assignment operator.
* @param __l An initializer_list.
*
* This function fills a %vector with copies of the elements in the
* initializer list @a __l.
*
* Note that the assignment completely changes the %vector and
* that the resulting %vector's size is the same as the number
* of elements assigned. Old data may be lost.
*/
vector&
operator=(initializer_list __l)
{
this->assign(__l.begin(), __l.end());
return *this;
}
在迭代器方面,C++11提供了几个返回const迭代器的接口,包括cbegin(), cend(), crbegin(), crend().同时提供了一个data()接口用于获取内存区域的首地址,之前如果要获得vector内存区域的首地址,做法应该是&(*begin()),因为没有办法保证vector迭代器就是一个指针,所以需要先解引用之后再去地址,而data()接口封装了相关的操作
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 464. Suggestion for new member functions in standard containers.
// data access
/**
* Returns a pointer such that [data(), data() + size()) is a valid
* range. For a non-empty %vector, data() == &front().
*/
#ifdef __GXX_EXPERIMENTAL_CXX0X__
_Tp*
#else
pointer
#endif
data() _GLIBCXX_NOEXCEPT
{ return std::__addressof(front()); }
#ifdef __GXX_EXPERIMENTAL_CXX0X__
const _Tp*
#else
const_pointer
#endif
data() const _GLIBCXX_NOEXCEPT
{ return std::__addressof(front()); }
C++11提供了一个shrink_to_fit接口用于削减冗余的内存空间.之前则需要用类似vector
#ifdef __GXX_EXPERIMENTAL_CXX0X__
/** A non-binding request to reduce capacity() to size(). */
void
shrink_to_fit()
{ _M_shrink_to_fit(); }
#endif
C++11还有两个新的接口emplace_back()和emplace();
#ifdef __GXX_EXPERIMENTAL_CXX0X__
void
push_back(value_type&& __x)
{ emplace_back(std::move(__x)); }
template
void
emplace_back(_Args&&... __args);
#endif
#ifdef __GXX_EXPERIMENTAL_CXX0X__
/**
* @brief Inserts an object in %vector before specified iterator.
* @param __position An iterator into the %vector.
* @param __args Arguments.
* @return An iterator that points to the inserted data.
*
* This function will insert an object of type T constructed
* with T(std::forward(args)...) before the specified location.
* Note that this kind of operation could be expensive for a %vector
* and if it is frequently used the user should consider using
* std::list.
*/
template
iterator
emplace(iterator __position, _Args&&... __args);
#endif
我觉得emplace应该是一个move版本的insert,而emplace_back是move版本的push_back,具体两者的实现在vector.tcc文件当中,在stl_vector.h里面只是一个前向声明而已.这两个接口的声明里面都使用到了C++11的新特性,可变模板(template variable),类似printf(...)的可变参数,但是这个是在模板当中的运用,编译器提供了这种核心特性的支持,处理的方式其实说白了就是递归的逐个处理,最后需要有一个模板参数为空的模板函数来作为结束递归的匹配(printf使用的是va_list相关的一些东西,我们可以用可变模板来实现一个类型安全的printf,感兴趣的同学可以找一找这份资料Andrei Alexandrescu的
#ifdef __GXX_EXPERIMENTAL_CXX0X__
template
template
void
vector<_Tp, _Alloc>::
emplace_back(_Args&&... __args)
{
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
{
_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
std::forward<_Args>(__args)...);
++this->_M_impl._M_finish;
}
else
_M_emplace_back_aux(std::forward<_Args>(__args)...);
}
#endif
#ifdef __GXX_EXPERIMENTAL_CXX0X__
template
template
void
vector<_Tp, _Alloc>::
_M_emplace_back_aux(_Args&&... __args)
{
const size_type __len =
_M_check_len(size_type(1), "vector::_M_emplace_back_aux");
pointer __new_start(this->_M_allocate(__len));
pointer __new_finish(__new_start);
__try
{
_Alloc_traits::construct(this->_M_impl, __new_start + size(),
std::forward<_Args>(__args)...);
__new_finish = 0;
__new_finish
= std::__uninitialized_move_if_noexcept_a
(this->_M_impl._M_start, this->_M_impl._M_finish,
__new_start, _M_get_Tp_allocator());
++__new_finish;
}
__catch(...)
{
if (!__new_finish)
_Alloc_traits::destroy(this->_M_impl, __new_start + size());
else
std::_Destroy(__new_start, __new_finish, _M_get_Tp_allocator());
_M_deallocate(__new_start, __len);
__throw_exception_again;
}
std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
_M_get_Tp_allocator());
_M_deallocate(this->_M_impl._M_start,
this->_M_impl._M_end_of_storage
- this->_M_impl._M_start);
this->_M_impl._M_start = __new_start;
this->_M_impl._M_finish = __new_finish;
this->_M_impl._M_end_of_storage = __new_start + __len;
}
#endif
#ifdef __GXX_EXPERIMENTAL_CXX0X__
template
template
typename vector<_Tp, _Alloc>::iterator
vector<_Tp, _Alloc>::
emplace(iterator __position, _Args&&... __args)
{
const size_type __n = __position - begin();
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage
&& __position == end())
{
_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
std::forward<_Args>(__args)...);
++this->_M_impl._M_finish;
}
else
_M_insert_aux(__position, std::forward<_Args>(__args)...);
return iterator(this->_M_impl._M_start + __n);
}
template
template
void
vector<_Tp, _Alloc>::
_M_insert_aux(iterator __position, _Args&&... __args)
剩下的其他一些东西应该跟侯捷锅的STL源码剖析差异不大.可能有一些细节上的变动,但是整体的思路是没问题的,侯捷锅的书对源代码做了相应的修改,便于阅读一些,直接读GCC库实现确实还是有点小蛋疼.不过,看多一点就习惯啦.一些命名也能找到相应的规律,比如_M其实的是成员函数,而_S是静态函数.一般带_都是内部用,不是暴露出来的接口等.最后还有一点要说明的是,我这里并没有吧所有的C++11版本的源代码变动都罗列出来,没有贴出来的基本都是构造函数,复制构造函数和赋值重载对move语义支持的那些.一些细节的函数也没有列出来.童鞋们如果要深入学习可以自己看看源代码,在bits目录下的stl_vector.h,vector.tcc,默认allocator是在ext目录下的*_allocator.h文件,上面有图的.
对于array,只是C++11加入的静态数组的支持,提供了一些比较方便的接口,分配内存是从栈里面去分配,跟T a[N]的效果是一样的.比较有趣的地方是,array支持传入参数长度为0构造一个数组,在实现当中对这种情况进行区分,如下
// Support for zero-sized arrays mandatory.
value_type _M_instance[_Nm ? _Nm : 1];
reference
back()
{ return _Nm ? *(end() - 1) : *end(); }
在array当中加入了对tuple相关接口的支持,具体是通过模板偏特化实现的
//1).tuple_size::value
template
struct tuple_size>
: public integral_constant { };
//2).tuple_element::type
template
struct tuple_element<_Int, array<_Tp, _Nm> >
{ typedef _Tp type; };
//3).get(array)
template
constexpr _Tp&
get(array<_Tp, _Nm>& __arr) noexcept
{ return __arr._M_instance[_Int]; }
template
constexpr _Tp&&
get(array<_Tp, _Nm>&& __arr) noexcept
{ return std::move(get<_Int>(__arr)); }
array同样提供了data()的接口可以返回内存的首地址.由于是静态数组,所以array没有提供太多复杂的接口.只有迭代器相关的接口和size(), data()等,另外重载了相关的比较操作符,方便使用.
末尾的吐槽:昨晚看豪斯医生,看到Cameron和Chase结婚,突然有很想找个妹纸的冲动-____-,好吧,也许是因为最近跟阿肥住在一起的缘故吧(擦,两个死宅死胖纸)
ps:昨晚看完1点半的时候,发现阿肥在打3C,跟电脑打(3C不像DOTA那样有AI,但是他还是在打....)..突然觉得有一股蛋蛋的忧伤.....