C++中std::allocator的使用

標準庫中包含一個名為allocator的類,允許我們將分配和初始化分離。使用allocator通常會提供更好的性能和更靈活的內存管理能力。

new有一些靈活性上的侷限,其中一方面表現在它將內存分配和對象構造組合在了一起。類似的,delete將對象析構和內存釋放組合在了一起。我們分配單個對象時,通常希望將內存分配和對象初始化組合在一起。因為在這種情況下,我們幾乎肯定知道對象應有什麼值。當分配一大塊內存時,我們通常計劃在這塊內存上按需構造對象。在此情況下,我們希望將內存分配和對象構造分離。這意味著我們可以分配大塊內存,但只在真正需要時才真正執行對象的創建操作(同時付出一定開銷)。一般情況下,將內存分配和對象構造組合在一起可能會導致不必要的浪費。

標準庫allocator類定義在頭文件memory中,它幫助我們將內存分配和對象構造分離開來。它提供一種類型感知的內存分配方法,它分配的內存是原始的、未構造的。類似vector,allocator是一個模板。為了定義一個allocator對象,我們必須指明這個allocator可以分配的對象類型。當一個allocator對象分配內存時,它會根據給定的對象類型來確定恰當的內存大小和對齊位置。allocator支持的操作,如下:

C++中std::allocator的使用

allocatro分配的內存是未構造的(unconstructed)。我們按需要在此內存中構造對象。在新標準庫中,construct成員函數接受一個指針和零個或多個額外參數,在給定位置構造一個元素。額外參數用來初始化構造的對象。類似make_shared的參數,這些額外參數必須是與構造的對象的類型相匹配的合法的初始化器。

在早期版本的標準庫中,construct只接受兩個參數:指向創建對象位置的指針和一個元素類型的值。因此,我們只能將一個元素拷貝到未構造空間中,而不能用元素類型的任何其它構造函數來構造一個元素。還未構造對象的情況下就使用原始內存是錯誤的。為了使用allocator返回的內存,我們必須用construct構造對象。使用未構造的內存,其行為是未定義的。

當我們用完對象後,必須對每個構造的元素調用destroy來銷燬它們。函數destroy接受一個指針,對執行的對象執行析構函數。我們只能對真正構造了的元素進行destroy操作。一旦元素被銷燬後,就可以重新使用這部分內存來保存其它string,也可以將其歸還給系統。釋放內存通過調用deallocate來完成。我們傳遞給deallocate的指針不能為空,它必須指向由allocate分配的內存。而且,傳遞給deallocate的大小參數必須與調用allocate分配內存時提供的大小參數具有一樣的值。

標準庫還為allocator類定義了兩個伴隨算法,可以在未初始化內存中創建對象。它們都定義在頭文件memory中,如下:

C++中std::allocator的使用

在C++中,內存是通過new表達式分配,通過delete表達式釋放的。標準庫還定義了一個allocator類來分配動態內存塊。分配動態內存的程序應負責釋放它所分配的內存。內存的正確釋放是非常容易出錯的地方:要麼內存永遠不會被釋放,要麼在仍有指針引用它時就被釋放了。新的標準庫定義了智能指針類型------shared_ptr、unique_ptr和weak_ptr,可令動態內存管理更為安全。對於一塊內存,當沒有任何用戶使用它時,智能指針會自動釋放它。現代C++程序應儘可能使用智能指針。

std::allocator是標準庫容器的默認內存分配器。你可以替換自己的分配器,這允許你控制標準容器分配內存的方式。

以上內容主要摘自:《C++Primer(Fifth Edition 中文版)》第12.2.2章節

下面是從其他文章中copy的測試代碼,詳細內容介紹可以參考對應的reference:

C++中std::allocator的使用#include "allocator.hpp"
#include <iostream>
#include <memory>
#include <string>
#include <vector>

namespace allocator_ {

////////////////////////////////////////////////
// reference: C++ Primer(Fifth Edition) 12.2.2
int test_allocator_1()
{

\tstd::allocator<:string> alloc; // 可以分配string的allocator對象
\tint n{ 5 };
\tauto const p = alloc.allocate(n); // 分配n個未初始化的string

\tauto q = p; // q指向最後構造的元素之後的位置
\talloc.construct(q++); // *q為空字符串
\talloc.construct(q++, 10, 'c'); // *q為cccccccccc
\talloc.construct(q++, "hi"); // *q為hi

\tstd::cout << *p << std::endl; // 正確:使用string的輸出運算符
\t//std::cout << *q << std::endl; // 災難:q指向未構造的內存
\tstd::cout << p[0] << std::endl;
\tstd::cout << p[1] << std::endl;
\tstd::cout << p[2] << std::endl;

\twhile (q != p) {
\t\talloc.destroy(--q); // 釋放我們真正構造的string
\t}

\talloc.deallocate(p, n);

\treturn 0;
}

int test_allocator_2()
{
\tstd::vector vi{ 1, 2, 3, 4, 5 };

\t// 分配比vi中元素所佔用空間大一倍的動態內存
\tstd::allocator alloc;
\tauto p = alloc.allocate(vi.size() * 2);
\t// 通過拷貝vi中的元素來構造從p開始的元素
\t/* 類似拷貝算法,uninitialized_copy接受三個迭代器參數。前兩個表示輸入序列,第三個表示
\t這些元素將要拷貝到的目的空間。傳遞給uninitialized_copy的目的位置迭代器必須指向未構造的

\t內存。與copy不同,uninitialized_copy在給定目的位置構造元素。
\t類似copy,uninitialized_copy返回(遞增後的)目的位置迭代器。因此,一次uninitialized_copy調用
\t會返回一個指針,指向最後一個構造的元素之後的位置。
\t*/
\tauto q = std::uninitialized_copy(vi.begin(), vi.end(), p);
\t// 將剩餘元素初始化為42
\tstd::uninitialized_fill_n(q, vi.size(), 42);

\treturn 0;
}

////////////////////////////////////////////////////////////
// reference: http://www.modernescpp.com/index.php/memory-management-with-std-allocator
int test_allocator_3()
{
\tstd::cout << std::endl;

\tstd::allocator intAlloc;

\tstd::cout << "intAlloc.max_size(): " << intAlloc.max_size() << std::endl;
\tint* intArray = intAlloc.allocate(100);

\tstd::cout << "intArray[4]: " << intArray[4] << std::endl;

\tintArray[4] = 2011;

\tstd::cout << "intArray[4]: " << intArray[4] << std::endl;

\tintAlloc.deallocate(intArray, 100);

\tstd::cout << std::endl;

\tstd::allocator<double> doubleAlloc;
\tstd::cout << "doubleAlloc.max_size(): " << doubleAlloc.max_size() << std::endl;

\tstd::cout << std::endl;

\tstd::allocator<:string> stringAlloc;
\tstd::cout << "stringAlloc.max_size(): " << stringAlloc.max_size() << std::endl;

\tstd::string* myString = stringAlloc.allocate(3);


\tstringAlloc.construct(myString, "Hello");
\tstringAlloc.construct(myString + 1, "World");
\tstringAlloc.construct(myString + 2, "!");

\tstd::cout << myString[0] << " " << myString[1] << " " << myString[2] << std::endl;

\tstringAlloc.destroy(myString);
\tstringAlloc.destroy(myString + 1);
\tstringAlloc.destroy(myString + 2);
\tstringAlloc.deallocate(myString, 3);

\tstd::cout << std::endl;

\treturn 0;
}

//////////////////////////////////////////////////////
// reference: http://en.cppreference.com/w/cpp/memory/allocator
int test_allocator_4()
{
\tstd::allocator a1; // default allocator for ints
\tint* a = a1.allocate(1); // space for one int
\ta1.construct(a, 7); // construct the int
\tstd::cout << a[0] << '\\n';
\ta1.deallocate(a, 1); // deallocate space for one int

\t// default allocator for strings
\tstd::allocator<:string> a2;

\t// same, but obtained by rebinding from the type of a1
\tdecltype(a1)::rebind<:string>::other a2_1;

\t// same, but obtained by rebinding from the type of a1 via allocator_traits
\tstd::allocator_traits<decltype>::rebind_alloc<:string> a2_2;
\tstd::string* s = a2.allocate(2); // space for 2 strings
\ta2.construct(s, "foo");
\ta2.construct(s + 1, "bar");
\tstd::cout << s[0] << ' ' << s[1] << '\\n';
\ta2.destroy(s);
\ta2.destroy(s + 1);
\ta2.deallocate(s, 2);
\treturn 0;
}
} // namespace allocator_/<decltype>
/<double>
/<vector>/<string>/<memory>/<iostream>

最後,如果你想學C/C++可以私信小編“01”獲取素材資料以及開發工具和聽課權限哦!


分享到:


相關文章: