STL標準庫是開發(fā)中的利器放可,也是開發(fā)的寶庫谒臼。
這次的源碼分析主要以GNU C++的2.9和4.9版本為例,因為4.9之后代碼重構耀里,核心部分發(fā)生了巨大的變化蜈缤,再次分別分析一下。
以GCC為例的標準庫位置: ....\x.x.x\include\c++\
對于2.9和4.9最大的差別其實就是冯挎,2.9主要采用了泛型編程的思想底哥,4.9引入了大量的面向對象編程的思想。
OOP(Object-Oriented Programming 面向對象編程) vs. GP(Generic Programming 泛型編程)
- OOP
- OOP主要的思想是將datas和methods關聯(lián)在一起的思想房官。
也就是數(shù)據放在類中趾徽,操作數(shù)據的方法也是放在類中。(就像我以前舉的一個例子翰守,如果class 貓
身上有毛孵奶,那么他必須有一個方法來管理他的毛,也就是舔毛()
這個函數(shù)潦俺。只需要貓咪.舔毛();
來調用這個函數(shù)拒课,就可以管理和操作對應的數(shù)據)
- OOP主要的思想是將datas和methods關聯(lián)在一起的思想房官。
- GP
- GP的主要思想是將datas和methods分開
在STL中大量使用到了GP的思想徐勃,來實現(xiàn)了數(shù)據和算法的分離,那么早像,算法如何才能操作數(shù)據呢僻肖,這中間的橋梁就是Iterators(迭代器)了,通過Iterator卢鹦,算法可以從容器中獲取到需要的數(shù)據臀脏,同樣也就可以起到操作數(shù)據的目的。
為何STL會采用GP的思想呢冀自?其實使用了GP思想揉稚,類和類之間的關系不會那么緊密,也就不會產生很強的耦合性熬粗,便于不同的成員搀玖,協(xié)同開發(fā)不同的模塊,有助于加快項目開發(fā)得效率驻呐,大家只需要依據“中間商”Iterator來編寫各自代碼就行了灌诅。
- GP的主要思想是將datas和methods分開
對于OOP來說最好的一點就是,方法和數(shù)據在同一個類中含末,那么方法是專門為類所設計的猜拾。比較方便能夠管理其中的數(shù)據。GP由于數(shù)據和方法分離佣盒,操作的時候挎袜,難免有些數(shù)據,不能被這個方法所操作肥惭。比如盯仪,list 不能使用::sort() 進行排序,那到底是為什么呢务豺?
- 看看::sort()的源碼磨总,發(fā)現(xiàn)問題所在
template <class RandomAccessIterator>
inline void sort(RandomAccessIterator first, RandomAccessIterator last)
{
if(first != last)
{
_introsort_loop(first, last, value_type(first), __lg(last-first)*2);
__final_insertion_sort(first, last);
}
}
.....
template <class RandomAccessIterator, class T, class Size>
void __introsort_loop(RandomAccessterator first, RandomAccessIterator last, T*, Size depth_limit)
{
......
RandomAccessIterator cut = __unguarded_partition(first, last, T(__median(*first, *(first + (last - first)/2), *(last - 1))));
//由于此處牽扯到了Iterator的下標運算
//list不是一個連續(xù)空間嗦明,前后節(jié)點之間靠指針相連笼沥,所以list 的Iterator不具備下表直接運算的能力,所以娶牌,list不能直接使用::sort()來進行排序
//也正是由于這個原因::sort() 只能為RandomAccessIterator來進行排序
......
}
- 那既然如此奔浅,在STL中,難道數(shù)據不適合就不能使用了诗良,是否有其他方式來使用呢汹桦?
- 以max()為例
//標準庫中的兩個函數(shù)
template<class T>
inline const T& max(const T& a, const T& b){
return a < b ? b: a;
}
template<class T, class Compare>
inline const T& max(const T& a, const T& b, Compare comp){
return comp(a, b)? b: a;
}
//如何使用
//定義一個依據長度比較大小的函數(shù)
bool strLonger(const T& a, const T& b){
return a.size() < s2.size();
}
cout << "max of zoo and hello:"
<< max(string("zoo"), string("hello")) << endl;
cout << "longer of zoo and hello: "
<< max(string("zoo"), string("hello"), strLonger) << endl;
分配器
分配器是容器管理內存的工具,對于容器的效率起著比較重要的作用
在正式開始說allocator之前鉴裹,先說幾句operator new()和 malloc()以及operator delete() 和free()
在創(chuàng)建對象時舞骆,會調用operator new()钥弯,而operator new()中分配內存實際也還是調用有C語言的Runtime Library所提供的malloc(),再由系統(tǒng)來分配所需要的內存督禽;銷毀對象時脆霎,則會使用operator delete(),而他實際會調用free()狈惫。
- vc中的operator new()
void *operator new (size_t size, const std::nothrow_t&)
{
void *p;
while((p = malloc(size)) == 0)
{
_TRY_BEGIN
if(_callnewh(size) == 0) break;
_CATCH(std::bad_alloc) return(0);
_CATCH_END
}
return (p);
}
malloc所分配的內存圖睛蛛,如上圖所示,其中藍色部分為真正需要的內存胧谈。其余部分為系統(tǒng)分配的管理這部分空間的配套內存忆肾,其中保存了需要的這塊內存的相關信息
灰色部分為調試模式系統(tǒng)分配的內存空間
根據vc版本,容器主要的使用的是allocator這個分配器的情況
template<class _Ty, class _a = allocator<_Ty> >
class vector{....}
template<class _Ty, class _a = allocator<_Ty> >
class list{....}
template<class _Ty, class _a = allocator<_Ty> >
class deque{....}
template<class _Ty, class _a = allocator<_Ty> >
class set{....}
template <class _Ty>
class allocator{
public:
typedef _SIZT size_type;
typedef _PDFT difference_type;
typedef _Ty _FARQ *pointer;
typedef _Ty value_type;
pointer allocate(size_type _N, const void *){return (_Allocate((difference_type)_N, (pointer)0)); }
void deallocate(void _FAQ *_P, size_type){operator delete(_P); }
}
///.....
//其中_Allocate()如下:
template<class _Ty> inline
_Ty _FARQ*_Allocate(_PDFT _N, _FARQ *){
if (_N < 0) _N = 0;
return (( _Ty _FARQ*) operator new ((_SIZT) _N * sizeof(_Ty)));
}
//如果使用allocator來申請內存
int *p = allocator<int>.allocate(512, (int*)0); //申請空間
allocator<int>().dellocate(p, 512);//釋放空間
由源代碼可以看出菱肖,VC分配器實際是通過operator new和delete來調用malloc和free來管理元素的內存
- GNU2.9的allocator也沒有過多的設計客冈,依然是通過::operator new和::operator delete來完成allocate()和deallocate(),但是稳强,在2.9版本中郊酒,實際容器使用的并非allocator,而是alloc
template<class _Ty, class _a = alloc >
class vector{....}
template<class _Ty, class _a = alloc >
class list{....}
template<class _Ty, class _a = alloc >
class deque{....}
template<class _Ty, class _a = alloc >
class set{....}
- alloc這個分配器的主要目的是為了減少malloc的調用次數(shù)
malloc申請空間時键袱,多余的空間的主要目的是為了free時能夠快速的知道申請的空間到底是多大燎窘。而對于容器來說,其中所保存的元素大小是相同的蹄咖,不需要在每個元素的前頭都記錄空間到底是多大褐健。 - alloc的解決方案:
- 設計了十六條鏈表,每條鏈表都負責對應大小的管理工作
- 元素的大小會被調整到8的倍數(shù)澜汤,然后在管理蚜迅,比如,50字節(jié)會被調整為56字節(jié)
- 第一條鏈表負責8個字節(jié)大小元素的部分俊抵,第二條鏈表負責16個字節(jié)大小元素的部分谁不,第三條負責24個字節(jié)大小元素的部分,以此類推徽诲,一直到第十六條鏈表刹帕,負責管理128字節(jié)的元素的部分
- 如果沒有內存,則一次性申請較大的空間谎替,然后將這些空間等分偷溺,所以相對于只malloc一次,則只有大空間具有那些額外的空間钱贯,而中間等分的部分實際上沒有那么多額外的空間的浪費
那么對于GNU4.5之后還在使用alloc這個分配器嗎挫掏?
template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector: protected _Vector_base<_Tp, _Alloc>{....}
#define __allocator_base __gnu_cxx::new_allocator
template<typename _Tp>
class allocator: public __allocator_base<_Tp>
{
.....
}
template<typename _Tp>
class new_allocator
{
...
pointer allocator(size_type __n, const void* = 0){
if(__n > this ->max_size())
std::__throw_bad_alloc();
return static_cast<_Tp*> (::operator new (_n * sizeof(_Tp)));
}
void deallocate(pointer __p, size_type){
::operator delete(__p);
}
...
}
- 在4.9版本以后,gnu的分配器也沒有特殊設計秩命,也是采用直接調用operator new來分配空間
- 之前設計的分配器被放入到了擴充分配器中(extention allocators)尉共,其中__pool_alloc就是GNU2.9的alloc褒傅,可以
vector<string,__gun::_cxx::__pool_alloc<string> > vec;
來使用
容器結構分類
-
序列式容器(Sequence Container)的衍生關系
- array
(C++2.0)連續(xù)空間
- vector
連續(xù)空間
- heap
以算法形式呈現(xiàn)(xxx_heap())
- priority_queue
- list
雙向鏈表
- slist
C++2.0中為forward_list,單向鏈表
- deque
分段連續(xù)空間
- stack
Container Adapter
- queue
Container Adapter
- stack
- array
-
關聯(lián)式容器(Associative Containers)的衍生關系(復合)
- rb_tree
紅黑樹,非公開
- set
- map
- multiset
- multimap
- hashtable
非公開
- hash_set
非標準袄友,C++2.0為unordered_set
- hash_map
非標準樊卓,C++2.0為unordered_map
- hash_multiset
非標準,C++2.0為unordered_multiset
- hash_mulitmap
非標準杠河,C++2.0為unordered_multimap
- hash_set
- rb_tree
容器 list
template <class T>
struct __list_node{
typedef void* void_pointer;
void_pointer prev;
void_pointer next;
T data;
};
template<class T, class Alloc = alloc>
class list{
protected:
typedef __list_node<T> list_node;
public:
typedef list_node* link_type;
typedef __list_iterator<T, T&, T*> iterator;
protected:
link_type node;
};
template<class T, class Ref, class Ptr>
struct __list_iterator{
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
}
- list為一個循環(huán)鏈表(如圖)碌尔,但是對于迭代器來說,end()獲取到的并非容器中的最后一個元素券敌,而應該是唾戚,最后一個元素之后的空元素,所以在list實現(xiàn)的時待诅,可以看到叹坦,end()指向了一個灰色的區(qū)域,這個區(qū)域實際就是end()指向的非容器內元素的區(qū)域
- 由于list非連續(xù)空間卑雁,所以Iterator在++時募书,如果不作調整,不會默認的移動到下一個不連續(xù)空間测蹲,所以莹捡,為了讓Iterator能夠和指針的用法相似,Iterator一定是一個class
template<class T, class Ref, class Ptr>
struct __list_iterator{
typedef __list_iterator(T, Ref, Ptr> self;
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;
typedef ptrdiff_t difference_type;
link_type nod;
reference operator*() const{
return (*node).data;
}
pointer operator->() const {
return &(operator*());
}
self& operator++(){//前++
node = (link_type)((*node).next); return *this;
}
self operator++(int){//后++扣甲,參數(shù)實際無意義
self temp = *this; ++*this; return tmp;
}
};
Iterator的設計原則
- 算法(algorithms)在操作容器(Container)中的數(shù)據需要通過Iterator知道的信息如下:
- iterator_category:Iterator的性質篮赢,例如是否可以雙向查詢
- difference_type:兩個Iterator之間的距離的type(int、unsigned int)琉挖,決定了容器可以容納多少元素
- value_type:元素本身的type
- reference:引用
- pointer:指針
在Iterator的設計時启泣,必須有這五種associated types
- traits的引入
- 如果Iterator不是一個class的情況,如果這樣的情況示辈,無法從一個指針中獲取以上的幾種類型寥茫,那么這時候,需要一個“中介”來去協(xié)調這件事矾麻,這時候就出現(xiàn)了一個traits的機制
- 這個traits可以區(qū)分到底是class設計的Iterator纱耻,也能夠區(qū)分是指針傳入的Iterator
//traits的設計
template<class I>
struct iterator_traits{
typedef typename I::value_type value_type;
typedef typename I::iterator_category
typedef typename I::difference_type
typedef typename I::pointer
typedef typename I::reference
};
//針對指針的兩種偏特化
template<class T>
struct iterator_traits<T*>{
typedef T value_type;
typedef random_access_iterator_tag iterator_category;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
};
template <class T>
struct iterator_traits<const T*>{
typedef T value_type;
typedef random_access_iterator_tag iterator_category;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
}
//traits的使用
template<typename I, ....>
void algorithm(......){
typename iterator_traits<I>::value_type v1;
}
根據偏特化,如果傳入的為指針就會自動進入偏特化的部分射富,那么就根據偏特化來獲取響應信息
-
各式各樣的traits以及對應的頭文件
- type traits : .../c++/type_traits
- iterator traits: .../c++/bits/stl_iterator.h
- char traits: .../c++/bits/char_traits.h
- allocator traits:.../c++/bits/alloc_traits.h
- pointer traits: .../c++/bits/ptr_traits.h
- array traits:.../c++/bits/array.h
容器Vector
- vector根據三個指針就可以控制全部內容 iterator start;膝迎、 iterator finish;、iterator end_of_storage;
其中finish指向最后一個元素之后的位置胰耗。
template <class T, class Alloc = alloc>
class vector
{
public:
typedef T value_type;
typedef value_type* iterator;
typedef value_tyle& reference;
typedef size_t size_type;
protected:
iterator start;
iterator finish;
iterator end_of_storage;
public:
iterator begin(){return start;}
iterator end() {return finish;}
size_type size() const{
return size_type(end() - begin());
}
size_type capacity() const {
return size_type(end_of_storage - begin());
}
bool empty() const {
return begin() == end();
}
reference operator[](size_type n){return *(begin() + n); }
reference front() {return *begin();}
reference back(){ return *(end() - 1); }
}
- 二倍成長
-
對于內存來說沒辦法實現(xiàn)原地擴充,因為前后都可能存在著其他程序的數(shù)據芒涡,如果擴充柴灯,意味著會要影響到其他程序卖漫,并且操作系統(tǒng)也不允許這樣干。那么對于vector來說赠群,hi如何來實現(xiàn)擴充的呢羊始?那么再擴充的時候,需要在內存的其他區(qū)域找到空間查描,在新找到的空間進行擴充完成后突委,再將數(shù)據復制到新開辟的空間中。而且每次增長的空間都是以兩倍作為基準冬三。
vector的內存圖
-
存入元素和兩杯增長的代碼
void push_back()(const T& x)
{
if(finish != end_of_storage){//尚有備用空間
construct(finish, x);
++finish;
}
else{
insert_aux(end(), x);
}
}
template<class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x){
if(finish != end_of_storage){//空間夠用
//在備用空間起始處建一個元素匀油,并以vector最后一個元素為其初值
construct(finish, *(finish - 1);
++finish;
T x_copy = x;
copy_backward(postion, finish - 2, finish - 1);
*postion = x_copy;
}
else{ //空間不夠用
const size_type old_size = size();
const size_type len = old_size != 0? 2*old_size: 1;
iterator new_start = data_allocator::allocate(len);
//以上分配原則:剮原大小為0,分配1勾笆;不為0敌蚜,分配原大小的兩倍;前半段用來放置原數(shù)據窝爪,后半段用來放置新數(shù)據
iterator new_finish = new start;
try{
//將原vector的內容拷貝到新的vector
new_finish = uninitialized_copy(start, position, new_start);
construnct(new_finish, x);//為新元素設置初值x
++new_finish;
//拷貝安插點后的原內容
new_finish = uninitialized_copy(postion, finish, new_finish);
}
catch(...){
destory(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throwl
}
//析構并釋放元vector
destory(begin(), end());
//調整迭代器弛车,指向新的vector
deallocate();
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
- GNU4.9之后的結構
容器array
- 沒有ctor,沒有dtor
template<typename _Tp, std::size_t _Nm>
struct array{
typedef _Tp;
typedef _Tp*;
typedef value_type*;
value_type _M_instance[_Nm? _Nm: 1];
iterator begin(){
return iterator(&_M_instance[0]);
}
iterator end(){
return iterator(&_M_instance[_Nm]);
}
}
forward_list
- 單向鏈表蒲每,具體可以參考list(雙向鏈表)