C++ Primer學習筆記 - 原始記憶體分配類allocator
阿新 • • 發佈:2021-12-05
目錄
為什麼會有allocator類
new將記憶體分配和物件構造組合到了一起,delete將物件析構和記憶體釋放頁組合到了一起。
當申請分配一大塊記憶體時,我們通常希望將記憶體分配和物件構建分離開。比如,下面將記憶體和物件構造組合到一起可能導致不必要的浪費:
string *const p = new string[n]; // 申請n個string記憶體空間,並呼叫default建構函式構造n個空的string物件 string s; string *q = p; while (cin >> s && q != p + n) *q++ = s; // 對*q賦新值 const size_t size = q - p; // 對陣列進行讀寫等操作 // ... delete[] p; // 析構陣列的每個物件元素,並釋放陣列空間
可以從上面程式碼看出,我們對申請的n個string記憶體空間的每個元素,都構建了2次string物件:一次是使用new申請記憶體的時候,另外一次是遍歷陣列對*q賦值的時候。
如何解決這個問題?避免2次物件的構建?
這就需要引入allocator類。
allocator類
標頭檔案
提供分配原始的、未構造的記憶體的方法,可以將記憶體分配和物件構造分離開來。
allocator是一個class template,我們這樣定義一個allocator物件並申請分配n個未初始化的string:
allocator<string> alloc; // 定義一個可以分配string的allocator物件 auto const p = alloc.allocate(n); // 分配n個原始string記憶體空間
allocator常用操作:
allocator<T> a;
a.allocate(n); // 分配一段原始的、未構造的記憶體空間,該空間為n個原始的T型別物件大小,並返回首地址
a.deallocate(p, n); // 釋放allocate分配的空間,首地址p,空間為n個T型別物件大小
a.construct(p, args); // 在p所指向的記憶體構造一個T物件,p必須是T*型別指標,指向一塊原始記憶體,args是構造T型別物件所需要引數
a.destroy(p); // 析構p所指向記憶體空間上的T物件,p必須是T*型別指標
allocator構造物件
allocator分配的記憶體是原始的、未構造的記憶體空間(稱為raw memory),要在其基礎上構造物件,就要使用constructor方法構造物件,用destroy方法析構物件。
// 定義allocator物件
allocator<string> alloc; // 定義一個可以分配string的allocator物件
auto const p = alloc.allocate(n); // 分配n個原始string記憶體空間
// 利用allocator物件,在raw memory上構造string物件
auto q = p;
alloc.construt(q++); // *q為空字串,並且q指向下一個待構造string物件首地址
alloc.construct(q++, 10, 'c'); // *q為cccccccccc
alloc.construct(q++, "hi"); // *q為hi
// 析構構造的物件
// 析構之後物件便銷燬了,記憶體重新變成raw memory
while (q != p)
alloc.destory(--q);
// 釋放記憶體空間,歸還給系統
// 釋放記憶體後不可再訪問
alloc.deallocate(p, n);
注意:deallocate釋放記憶體前,最好先呼叫destory析構物件,因為構造的物件可能包含其他資源依賴於物件的解構函式釋放。
拷貝和填充raw memory的演算法
除了可以利用allocator的constructor在raw memory上構造物件,還可以用標準庫的2個伴隨演算法(uninitialized_copy和uninitialized_fill)來對raw memory進行填充。
#include <memory>
uninitialized_copy(b, e, b2); // 從指定迭代器範圍[b, e)拷貝所有元素到b2指向的raw memory中。b2指向的memory必須足夠大,以容納輸入序列中元素拷貝。返回copy後的目的位置迭代器
uninitialized_copy_n(b, n, b2); // 從指定迭代器範圍[b, b+n)拷貝n個元素到b2指向的raw memory中。返回copy後的目的位置迭代器
unintialized_filled(b, e, t); // 在指定迭代器範圍[b, e)中建立物件,所有物件值均為t的拷貝。返回填充後的目的位置迭代器
unintialized_filled_n(b, n, t); // 在指定迭代器範圍[b, b+n)中建立n個物件,所有物件值均為t的拷貝。返回填充後的目的位置迭代器
例如,
vector<int> vi = {1,2,3,4,5,6,7,8,9};
allocator<int> alloc;
auto p = alloc.allocate(vi.size() * 2); // 申請vi 2倍大尺寸的raw memory
auto q = unintialized_copy(vi.begin(), vi.end(), p); // 拷貝vi所有元素到p開頭的記憶體塊中
unintialized_fill_n(q, vi.size(), 42); // 將p的剩餘空間全部填充位42
我們用allocator重新開頭new寫的那段程式:
/* 原始程式 */
int n = 10;
string *const p = new string[n]; // 申請n個string記憶體空間,並呼叫default建構函式構造n個空的string物件
string s;
string *q = p;
while (cin >> s && q != p + n)
*q++ = s; // 對*q賦新值
const size_t size = q - p;
// 對陣列進行讀寫等操作
// ...
delete[] p; // 析構陣列的每個物件元素,並釋放陣列空間
/* allocator改寫程式 */
int n = 10;
allocator<string> alloc;
auto p = alloc.allocate(n);
string s;
auto q = p;
while (cin >> s && q != p + n)
{
alloc.construct(q++, s);
}
alloc.deallocate(p, n);