在前面的博客《C++ STL學習筆記(3) 分配器Allocator,OOP, GP簡單介紹》中,簡單的介紹了分配器再STL的容器中所擔當的角色,這一節對STL六大部件之一的分配器進行詳細的學習,從STL源代碼的角度去理解分配器的具體細節:
operator new 和malloc
在不同的平臺上調用C++中的operator new,最終都會是去調用malloc去分配內存,而malloc又會根據不同的操作系統,去調用相應的API分配內存。malloc分配的內存長什麼樣子?
其中,size是申請的內存的大小,但是malloc會在此基礎上搭配其他的東西,然後將內存地址返回。
灰色部分表示: 在debug_mode 模式下會添加的東西
紅色部分表示: 在上下兩部分添加的cookie,記錄的是分配的內存的大小,因爲在free的時候,只是傳入了指針,而free就是通過這個區域獲得要釋放的內存的大小。
綠色部分表示:加上上述部分之後調整到某個邊界(這部分只是大概提了一下)
從上圖可以看出,malloc實際上返回給我們的東西多餘我們申請的東西。
VC6中allocator的實現:
例如,調用allocator分配內存:
int* p = allocator<int>().allocate(512, (int*)0); // 創建一個臨時對象 分配內存
allocator<int>().deallocate(p, 512); // 釋放內存
Allocator對於內存的分配會有額外的開銷,如上面上所述,實際分配的內存大於所需的內存(添加了額外的部分),Gnu2.9所附的標準庫中,有一個實現更優的分配器alloc,它的行爲模式如下:
Gnu的分配器爲了實現節省內存,他需要減少malloc的次數,所以設計了16條鏈表,每條鏈表負責不同大小的區塊,第0條鏈表負責的是8字節大小區塊,第一條負責16字節大小的區塊,以此類推....所以在容器需要內存的時候,內存中元素的大小會被分配器調整到8的倍數,然後再具體確定選擇哪一條鏈表。
分配器主要是爲容器服務/
容器結構與分類:
適配器的理解:
deque --> stack, queue相當於deque的適配器,因爲stack, queue中用到了deque,所以將它們稱爲適配器更加合適。
1. 容器list
節點的設計:
關於迭代器:
扮演指針的功能,定義爲一個類:
對於vector,array這種連續內存的容器的迭代器,和其他內存不連續的對象的迭代器,是有一定的區別的。除此之外迭代器需要重載->, *, ++, --等運算符。
Iterator需要遵循的原則:
traits: 人爲設計的一種萃取機,萃取出特徵,有type_traits, character_traits, pointer_traits, 那麼iterator_traits又是什麼呢?
所以iterator_traits的作用是萃取出iterator的特徵,那麼iterator有什麼特性呢?
前面講過,iterator是算法和容器之間的橋樑,它讓算法知道要處理的元素的範圍,在算法知道要處理的元素的範圍後,算法需要對元素進行一些操作,在這些操作的過程中,算法很可能需要知道這個iterator有哪些性質,從而選擇一個最佳化的動作對元素進行操作,例如:
舉個例子,有一個rotate函數,
可以看到rotate 函數調用了std::_rotate 和std::_iterator_category
可以看到std::_iterator_category的定義:
可以看到std::_iterator_category中調用了iterator_traits<Iter>::iterator_category,可以看到這是要萃取迭代器Iter的iterator_category特徵,也就是迭代器的分類(關於迭代器的分類前面講過,有輸入迭代器,輸出迭代器,前向迭代器,雙向迭代器,以及random access迭代器)。
Input iterator(輸入迭代器) | 讀,不能寫 | 只支持自增運算 |
Output iterator(輸出迭代器) ; | 寫,不能讀 | 只支持自增運算 |
Forward iterator(前向迭代器) | 讀和寫; | 只支持自增運算 |
Bidirectional iterator(雙向迭代器) ; | 讀和寫 | 支持自增和自減運算 |
Random access iterator(隨機訪問迭代器) | 讀和寫; | 支持完整的迭代器算術運 算 |
(查了一下我沒記錯,就是這五種)
所以說,rotate算法知道了迭代器的分類,以便他能夠採取最佳的操作方式,
接着看std::_rotate函數的定義:
可以看到,這裏也調用了iterator_traits,分別獲迭代器(random access)的value_type, 以及difference_type
value_type: iterator所指向的元素的類型
difference_type: 兩個迭代器之間的距離,應該用什麼數據類型來表現
(C++標準庫中,共有五種迭代器特性,除了上面的terator_category, value_type, value_type, 還有reference_type以及pointer_type兩種特徵, 稱之爲迭代器的associative types,相關類型 , 作爲一個迭代必須設計出這五種特性,以便能夠回答算法的提問。)
可以看到,在rotate算法中,它需要知道iterator的三個特徵。
舉一個例子,list的迭代器的實現如下所示:
可以看到。它實現了迭代器的5種associative types, 能夠滿足算法的提問。
算法中對iterator的五種類型的獲取:
如果算法接收到的迭代器不是class,而是一個native pointer(也就是C++中的自然指針,這也是C++迭代器,退化的迭代器),那麼此時迭代器是如何回答算法的提問的呢?
C++通過添加一箇中間層(萃取機)來解決這一問題:(侯捷老師說:解決計算機問題的尚方寶劍:添加一箇中介層)
所以iterator_traits用來分離class_iterators和non_class_iterators:
可以看到iterator的定義如下,出了泛化的定義之外,還有特化的版本:
從上面的iterator_traits的定義可以看到它作爲一箇中間層的分離功能。
可以順便看一下迭代器完整的定義:
在C++ STL中,還有其他的各種Traits
關於traits的知識就介紹到這裏
---------------------------------------------------------------分割線-----------------------------------------------------------------