淺談C++ allocator記憶體管理(對比new的侷限性)
STL中,對記憶體管理的alloc的設計,迫使我去學習了allocator類。這裡對allocator記憶體管理做了點筆記留給自己後續查閱。allocator類宣告、定義於標頭檔案<memory>
中的std
名稱空間內。所以,應該有以下內容位於檔案頭部…
#include <memory>
using namespace std;
文章目錄
1. 我們所知道的malloc和new
再此之前,我只知道兩種開闢記憶體的方式。
其一,可以使用C語言的函式malloc、realloc、calloc開闢記憶體,舉個例子:
int* ptr = (int *)malloc(10 * sizeof(int)); /* 進行強制型別轉換 */
其二,可以使用C++方式開闢記憶體,比如:
int* ptr = new int[10];
對於,C風格的記憶體管理這裡不做討論。但是,對於C++的new風格,這裡總結一下它的侷限性。對於我個人來說,我一般寫成這樣new int[10]()
,也就是在最後加一對小括號,因為C++保證這樣可以將int[10]
2. C++中new的侷限性
對於以上這一段話,有一個話題需要弄清楚的,就是——記憶體構造。
對C++的new而言,它首先會(1)分配記憶體,然後自動的完成(2)物件構造。這裡可以用侯捷先生翻譯的《深度探索C++物件模型》一書中的虛擬碼來表示new的過程:
Point* heap = __new(sizeof(Point));//開闢記憶體
if (head != 0) {
head->Point::Point();//物件構造(記憶體構造)
}
注意,__new不表示new(它只是完成記憶體申請),以上整個虛擬碼過程為new所完成的功能。
正是因為new的這一連串的操作,造成了效能的下降。比如,
auto p = new string[100];
for (int i = 0; i < 5; ++i){
p[i] = "balaba...";
}
實際上,我只需要5個string,而new把100個物件全部構造好了(每個string已經被初始化為空字串,也就是""
)。
然後,接著又將p[0-4]賦值為balaba…
也就是前面將p[0-4]賦值為空字串的操作,變得毫無意義。
3. 使用allocator將記憶體分配、物件構造分離開
既然,new有它自身的侷限性。對於效能要求極高的STL肯定是不會使用new的。好在有一個allocator類——它也是一個模板類,同時就是用來處理記憶體問題的。
allocator類將new的記憶體分配、物件構造,視作兩個獨立的過程,並由獨立的函式負責。舉個例子:
allocator<char> str;
char* base = str.allocate(10), *p = base; //記憶體分配
str.construct(p++, 'a'); //物件構造並初始化
str.construct(p++, 'b');
cout << base[0] << base[1];
因為allocator是模板類,所以需要指定型別。接著,呼叫allocate(10)
函式來分配記憶體(申請了10個char記憶體)。然後,使用construct
函式構造base[0]這塊記憶體,並賦以初值a
。
這就將new記憶體分配、記憶體構造給分離開了。一切,都像我們看到的那樣。
同樣,將delete的過程也拆分了開來。這是必須的,我們不能用delete去釋放allocate分配的記憶體。
str.destroy(--p); //銷燬物件
str.destroy(--p);
str.deallocate(base, 10); //釋放記憶體
小結:如果要使用allocate返回的記憶體,那麼就必須先construct構造它。否則,你後續的行為都是未定義的(造成的後果也是嚴重的)。
但是,有幾個例外。它們是uninitialized_copy、uninitialized_copy_n、uninitialized_fill和uninitialized_fill_n。從名字就知道uninitialized
(未初始化的),它們的引數必須指向的是未構造的記憶體,比如uninitialized_copy會在給定位置構造元素。
有興趣可以閱讀《動態記憶體管理allocator類C++ STL標準模板庫vector實現》看看它們的用法。
2018-12-23 北京 海淀
Reference:
[1] C++ Primer(第5版)
[2] 深度探索C++物件模型