1. 程式人生 > 其它 >C++ Primer學習筆記 - 原始記憶體分配類allocator

C++ Primer學習筆記 - 原始記憶體分配類allocator

目錄

為什麼會有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);