C++ allocator類學習理解


前言

在學習STL中containers會發現C++ STL里定義了很多的容器(containers),每一個容器的第二個模板參數都是allocator類型,而且默認參數都是allocator。但是allocator到底是什么?有什么作用呢?

接下來就圍繞着是什么和有什么作用來展開,其中最后補充一下如何去使用默認的allocator。

由於本人學習尚淺,各種blog和msdn學習了幾天,依然還是不是特別理解。這是把自己的學習經驗,進行一次梳理和記錄。

 

What?

 

std::allocator

template <class T> class allocator;    // 默認分配器

 

默認分配器

所有的分配器都定義在 <memory> 頭文件中,被用於標准庫中的STL containers

如果標准容器中最后一個模板參數沒有指定,那么就是allocator默認參數

 對分配或釋放存儲的成員函數的調用將發生在一個總的順序中,並且每個這樣的釋放將在下一個分配(如果有的話)之前發生。
 

 主要成員函數

  • address
     
     函數原型:
     pointer address (reference x) const noexcept;
     
     const_pointer address ( const_referece x );
     
     //    reference => T&          const_reference => const T&

  返回x的地址

 

  • allocate
     
     函數原型:
     pointer allocate(size_type n, allocator<void>::const_pointer hint = 0);
     //hint:0 or 通過另外一個allocate獲得的非零值且沒有使用deallocate釋放
     //當 hint != 0 時,這個值可以作為一個提示,通過分配接近指定的新存儲塊來提高性能。相鄰元素地址是一個不錯的選擇
     pointer => T* const_pointer => const T* size_type => size_t
     
     分配存儲塊
     嘗試分配n個T類型的存儲空間,然后返回第一個元素的起始地址
     只是分配空間,不構造對象
     
     在標准默認allocator,存儲塊是使用 一次或多次 ::operator new 進行分配,如果他不能分配請求的存儲空間,則拋出bad_alloc異常

 

  • construct

     原型函數:
     template <class U, class... Args>
     void construct(U* p, Args&&... args);

     在p指向的位置構建對象U,此時該函數不分配空間,pointer p是allocate分配后的起始地址
     constructor將其參數轉發給相應的構造函數構造U類型的對象,相當於 ::new ((void*) p) U(forward<Args> (args)...);

 

  • deallocate
     
     原型函數:
     void deallocate(pointer p, size_t n);
     
     釋放先前allocate分配的且沒有被釋放的存儲空間
     
     p:指向以前使用allocator :: allocate分配的存儲塊的指針。
     n:在調用allocator :: allocate時為這個存儲塊分配的元素數量。
     

     在默認的allocator中,使用 ::operator delete進行釋放

 

  • destroy
     
     原型函數:
     template <class U>void destroy (U* p);
     
     銷毀p指向的對象,但是不會釋放空間,也就意味着,這段空間依然可以使用
     該函數使用U的析構函數,就像使用下面的代碼一樣:P->〜U();

 

  • max_size

     原型函數:
     size_type max_size() const noexcept;

     返回最大可能分配的大小

How?

有關allocator的最重要的事實是它們只是為了一個目的:封裝STL容器在內存管理上的低層細節。你不應該在自己的代碼中直接調用 allocator 的成員函數,除非正在寫一個自己的STL容器。你不應該試圖使用allocator來實現operator new[];這不是allocator該做的。 如果你不確定是否需要使用allocator,那就不要用。

 

基本上很少有人會自定義一個allocator。一來,默認的allocator已經夠用了;二來,確實不知道該怎么用。一般來說,我們沒有必要重新定義一個allocator。自定義的方式主要是為了提高內存分配相關操作的性能。而STL提供的方式性能已經足夠好了。

 

使用默認allocator

使用步驟:

由於allocator將內存空間的分配和對象的構建分離,故使用allocator分為以下幾步:

  1. allocator與類綁定,因為allocator是一個泛型類
  2. allocate()申請指定大小空間
  3. construct()構建對象,其參數為可變參數,所以可以選擇匹配的構造函數
  4. 使用,與其它指針使用無異
  5. destroy()析構對象,此時空間還是可以使用
  6. deallocate()回收空間

請認真遵守這個順序使用,不然會無法預料的異常

( 下面該程序也可以解決無默認參數來構造對象數組的問題)

//#include "CAnimal.h"
#include <memory>
#include <iostream>

using namespace std;

class Animal
{
public:
#if 1        //即使為0,沒有默認構造也是可以,
    Animal() : num(0)
    {
        cout << "Animal constructor default" << endl;
    }
#endif
    Animal(int _num) : num(_num)
    {
        cout << "Animal constructor param" << endl;
    }

    ~Animal()
    {
        cout << "Animal destructor" << endl;
    }

    void show()
    {
        cout << this->num << endl;
    }

private:
    int num;
};

int main()
{
    allocator<Animal> alloc;        //1.
    Animal *a = alloc.allocate(5);    //2.

    //3.
    alloc.construct(a, 1);
    alloc.construct(a + 1);
    alloc.construct(a + 2, 3);
    alloc.construct(a + 3);
    alloc.construct(a + 4, 5);

    //4.
    a->show();
    (a + 1)->show();
    (a + 2)->show();
    (a + 3)->show();
    (a + 4)->show();

    //5.
    for (int i = 0; i < 5; i++)
    {
        alloc.destroy(a + i);
    }
    //對象銷毀之后還可以繼續構建,因為構建和內存的分配是分離的
    //6.
    alloc.deallocate(a, 5);

    cin.get();
    return 0;
}

通過運行結果可以看出,無論是否有默認構造,allocator會選擇出最匹配的構造函數(重載) 

 

結語:由於現在自己木有工作經驗和項目經驗,實在對這個allocator的使用,懵懵懂懂,在使用STL containers時,也沒有看見自定義的Allocator,現在只能簡單學習了解,以便以后工作撿起來不那么難或者在看大神的代碼的時候不在那么懵逼。。。

好吧!就這樣吧。。。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM