Allocator是C++語(yǔ)言標(biāo)準(zhǔn)庫(kù)中最神秘的部分之一。它們很少被顯式使用,標(biāo)準(zhǔn)也沒(méi)有明確出它們應(yīng)該在什么時(shí)候被使用。今天的allocator與最初的STL建議非常不同,在此過(guò)程中還存在著另外兩個(gè)設(shè)計(jì)--這兩個(gè)都依賴于語(yǔ)言的一些特性,而直到最近才在很少的幾個(gè)編譯器上可用。對(duì)allocator的功能,標(biāo)準(zhǔn)似乎在一些方面追加了承諾,而在另外一些方面撤銷了承諾。

    這篇專欄文章將討論你能用allocator來(lái)做什么以及如何定義一個(gè)自己的版本。我只會(huì)討論C++標(biāo)準(zhǔn)所定義的allocator:引入準(zhǔn)標(biāo)準(zhǔn)時(shí)代的設(shè)計(jì),或試圖繞過(guò)有缺陷的編譯器,只會(huì)增加混亂。
什么時(shí)候不使用Allocator

    C++標(biāo)準(zhǔn)中的Allocator分成兩塊:一個(gè)通用需求集(描述于§ 20.1.5(表 32)),和叫std::allocator的class(描述于§20.4.1)。如果一個(gè)class滿足表32的需求,我們就稱它為一個(gè)allocator。std::allocator類滿足那些需求,因此它是一個(gè)allocator。它是標(biāo)準(zhǔn)程序庫(kù)中的唯一一個(gè)預(yù)先定義allocator類。

    每個(gè) C++程序員都已經(jīng)知道動(dòng)態(tài)內(nèi)存分配:寫(xiě)下new X來(lái)分配內(nèi)存和創(chuàng)建一個(gè)X類型的新對(duì)象,寫(xiě)下delete p來(lái)銷毀p所指的對(duì)象并歸還其內(nèi)存。你有理由認(rèn)為allocator會(huì)使用new和delete--但它們沒(méi)有。(C++標(biāo)準(zhǔn)將::operator new()描述為“allocation function”,但很奇怪,allocator并不是這樣的。)

    有關(guān)allocator的最重要的事實(shí)是它們只是為了一個(gè)目的:封裝STL容器在內(nèi)存管理上的低層細(xì)節(jié)。你不應(yīng)該在自己的代碼中直接調(diào)用allocator的成員函數(shù),除非正在寫(xiě)一個(gè)自己的STL容器。你不應(yīng)該試圖使用allocator來(lái)實(shí)現(xiàn)operator new[];這不是allocator該做的。 如果你不確定是否需要使用allocator,那就不要用。

    allocator是一個(gè)類,有著叫allocate()和deallocate()成員函數(shù)(相當(dāng)于malloc和free)。它還有用于維護(hù)所分配的內(nèi)存的輔助函數(shù)和指示如何使用這些內(nèi)存的typedef(指針或引用類型的名字)。如果一個(gè)STL容器使用用戶提供的allocator來(lái)分配它所需的所有內(nèi)存(預(yù)定義的STL容器全都能這么做;他們都有一個(gè)模板參數(shù),其默認(rèn)值是std::allocator),你就能通過(guò)提供自己的allocator來(lái)控制它的內(nèi)存管理。

    這種柔性是有限制的:仍然由容器自己決定它將要申請(qǐng)多少內(nèi)存以及如何使用它們。在容器申請(qǐng)更多的內(nèi)存時(shí),你能控制它調(diào)用那個(gè)低層函數(shù),但是你不能通過(guò)使用allocator來(lái)讓一個(gè)vector行動(dòng)起來(lái)像一個(gè)deque一樣。雖然如此,有時(shí)候,這個(gè)受限的柔性也很有用。比如,假設(shè)你有一個(gè)特殊的fast_allocator,能快速分配和釋放內(nèi)存(也許通過(guò)放棄線程安全性,或使用一個(gè)小的局部堆),你能通過(guò)寫(xiě)下std::list<T, fast_allocator<T> >而不是簡(jiǎn)單的std::list<T>來(lái)讓標(biāo)準(zhǔn)的list使用它。

    如果這看起來(lái)對(duì)你很陌生,那就對(duì)了。沒(méi)有理由在常規(guī)代碼中使用allocator的。
定義一個(gè)Allocator

    關(guān)于allocator的這一點(diǎn)你已經(jīng)看到了:它們是模板。Allocator,和容器一樣,有value_type,而且allocator的value_type必須要匹配于使用它的容器的value_type。這有時(shí)會(huì)比較丑陋:map的value_type相當(dāng)復(fù)雜,所以顯式調(diào)用allocator的map看起來(lái)象這樣的,std::map<K,V, fast_allocator<std::pair<const K, V> > >。(像往常一樣,typedef會(huì)對(duì)此有幫助。)

    以一個(gè)簡(jiǎn)單的例子開(kāi)始。根據(jù)C++標(biāo)準(zhǔn),std::allocator構(gòu)建在::operator new()上。如果你正在使用一個(gè)自動(dòng)化內(nèi)存使用跟蹤工具,讓std::allocator更簡(jiǎn)單些會(huì)更方便。我們可以用malloc()代替::operator new(),而且我們也不考慮(在好的std::allocator實(shí)作中可以找到的)復(fù)雜的性能優(yōu)化措施。我們將這個(gè)簡(jiǎn)單的allocator叫作malloc_allocator 。

    既然malloc_allocator的內(nèi)存管理很簡(jiǎn)單,我們就能將重點(diǎn)集中在所有STL的allocator所共有的樣板上。首先,一些類型:allocator是一個(gè)類模板,它的實(shí)例專為某個(gè)類型T分配內(nèi)存。我們提供了一序列的typedef,以描述該如何使用此類型的對(duì)象:value_type指T本身,其它的則是有各種修飾字的指針和引用。
template <class T> class malloc_allocator

    {

    public:

      typedef T                 value_type;

      typedef value_type*       pointer;

      typedef const value_type* const_pointer;

      typedef value_type&       reference;

      typedef const value_type& const_reference;

      typedef std::size_t       size_type;

      typedef std::ptrdiff_t    difference_type;

      ...

    };
這些類型與STL容器中的很相似,這不是巧合:容器類常常直接從它的allocator提取這些類型。

    為 什么有這么多的typedef?你可能認(rèn)為pointer是多余的:它就是value_type *。絕大部份時(shí)候這是對(duì)的,但你可能有時(shí)候想定義非傳統(tǒng)的allocator,它的pointer是一個(gè)pointer-like的class,或非標(biāo)的 廠商特定類型value_type __far *;allocator是為非標(biāo)擴(kuò)展提供的標(biāo)準(zhǔn)hook。不尋常的pointer類型也是存在address()成員函數(shù)的理由,它在 malloc_allocator中只是operator &()的另外一種寫(xiě)法:

現(xiàn)在我們能開(kāi)始真正的工 作:allocate()和deallocate()。它們很簡(jiǎn)單,但并不十分象malloc()和free()。我們傳給allocate()兩個(gè)參 數(shù):我們正在為其分派空間的對(duì)象的數(shù)目(max_size返回可能成功的最大請(qǐng)求值),以及可選的一個(gè)地址值(可以被用作一個(gè)位置提示)。象 malloc_allocator這樣的簡(jiǎn)單的allocator沒(méi)有利用那個(gè)提示,但為高性能而設(shè)計(jì)的allocator可能會(huì)利用它。返回值是一個(gè)指 向內(nèi)存塊的指針,它足以容納n個(gè)value_type類型的對(duì)象并有正確的對(duì)齊。

    我 們也傳給deallocate()兩個(gè)參數(shù):當(dāng)然一個(gè)是指針,但同樣還有一個(gè)元素計(jì)數(shù)值。容器必須自己掌握大小信息;傳給allocate和 deallocate的大小參數(shù)必須匹配。同樣,這個(gè)額外的參數(shù)是為效率而存在的,而同樣,malloc_allocator不使用它。

template <class T> class malloc_allocator

    {

    public:

      pointer allocate(size_type n, const_pointer = 0) {

        void* p = std::malloc(n * sizeof(T));

        if (!p)

          throw std::bad_alloc();

        return static_cast<pointer>(p);

      }

      void deallocate(pointer p, size_type) {

        std::free(p);

      }

      size_type max_size() const {

        return static_cast<size_type>(-1) / sizeof(value_type);

      }

      ...

    };
allocate()和 deallocate()成員函數(shù)處理的是未初始化的內(nèi)存,它們不構(gòu)造和銷毀對(duì)象。語(yǔ)句a.allocate(1)更象 malloc(sizeof(int))而不是new int。在使用從allocate()獲得的內(nèi)存前,你必須在這塊內(nèi)存上創(chuàng)建對(duì)象;在通過(guò)deallocate()歸還內(nèi)存前,你需要銷毀那些對(duì)象。

    C++ 語(yǔ)言提供一個(gè)機(jī)制以在特定的內(nèi)存位置創(chuàng)建對(duì)象:placement new。如果你寫(xiě)下new(p) T(a, b),那么你正在調(diào)用T的構(gòu)造函數(shù)產(chǎn)生一個(gè)新的對(duì)象,一如你寫(xiě)的new T(a, b)或 T t(a, b)。區(qū)別是當(dāng)你寫(xiě)new(p) T(a, b),你指定了對(duì)象被創(chuàng)建的位置:p所指向的地址。(自然,p必須指向一塊足夠大的內(nèi)存,而且必須是未被使用的內(nèi)存;你不能在相同的地址構(gòu)建兩個(gè)不同的對(duì) 象。)。你也可以調(diào)用對(duì)象的析構(gòu)函數(shù),而不釋放內(nèi)存,只要寫(xiě)p->~T()。

    這 些特性很少被使用,因?yàn)橥ǔ?nèi)存的分配和初始化是一起進(jìn)行的:使用未初始化的內(nèi)存是不方便的和危險(xiǎn)的。你需要如此低層的技巧的很少幾處之一就是你在寫(xiě)一個(gè) 容器類,于是allocator將內(nèi)存的分配與初始化解耦。成員函數(shù)construct()調(diào)用placement new,而且成員函數(shù)destory()調(diào)用析構(gòu)函數(shù)。

template <class T> class malloc_allocator

    {

    public:

      void construct(pointer p, const value_type& x) {

        new(p) value_type(x);

      }

      void destroy(pointer p) { p->~value_type(); }

      ...

    };

 (為什么allocator有那些成員 函數(shù),什么時(shí)候容器可以直接使用placement new?一個(gè)理由是要隱藏笨拙的語(yǔ)法,而另一個(gè)是如果寫(xiě)一個(gè)更復(fù)雜的allocator時(shí)你可能想在構(gòu)造和銷毀對(duì)象時(shí)construct()和 destroy()還有其它一些副作用。比如,allocator可能維護(hù)一個(gè)所有當(dāng)前活動(dòng)對(duì)象的日志。)

    這 些成員函數(shù)沒(méi)有一個(gè)是static的,因此,容器在使用allocator前做的第一件事就是創(chuàng)建一個(gè)allocator對(duì)象--也就是說(shuō)我們應(yīng)該定義一 些構(gòu)造函數(shù)。但是,我們不需要賦值運(yùn)算:一旦容器創(chuàng)建了它的allocator,這個(gè)allocator就從沒(méi)想過(guò)會(huì)被改變。表32中的對(duì) allocator的需求沒(méi)有包括賦值。只是基于安全,為了確保沒(méi)人偶然使用了賦值運(yùn)算,我們將禁止掉這個(gè)可能自動(dòng)生成的函數(shù)。

template <class T> class malloc_allocator

    {

    public:

      malloc_allocator() {}

      malloc_allocator(const malloc_allocator&) {}

      ~malloc_allocator() {}

    private:

      void operator=(const malloc_allocator&);

      ...

    };
這些構(gòu)造函數(shù)實(shí)際上沒(méi)有做任何事,因?yàn)檫@個(gè)allocator不需要初始化任何成員變量。基于同樣的理由,任意兩個(gè)malloc_allocator都是 可互換的;如果a1和a2的類型都是malloc_allocator<int>,我們可以自由地通過(guò)a1來(lái)allocate()內(nèi)存然后通 過(guò)a2來(lái)deallocate()它。我們因此定義一個(gè)比較操作以表明所有的malloc_allocator對(duì)象是等價(jià)的:
template <class T>

    inline bool operator==(const malloc_allocator<T>&,

                           const malloc_allocator<T>&) {

      return true;

    }

    template <class T>

    inline bool operator!=(const malloc_allocator<T>&,

                           const malloc_allocator<T>&) {

      return false;

    }
你會(huì)期望一個(gè)allocator,它的不 同對(duì)象是不可替換的嗎?當(dāng)然--但很難提供一個(gè)簡(jiǎn)單而有用的例子。一種明顯的可能性是內(nèi)存池。它對(duì)大型的C程序很常見(jiàn),從幾個(gè)不同的位置(“池”)分配內(nèi) 存,而不是什么東西都直接使用malloc()。這樣做有幾個(gè)好處,其一是it only takes a single function call to reclaim all of the memory associated with a particular phase of the program。 使用內(nèi)存池的程序可能定義諸如mempool_Alloc和mempool_Free這樣的工具函數(shù),mempol_Alloc(n, p)從池p中分配n個(gè)字節(jié)。很容易寫(xiě)出一個(gè)mmepool_alocator以匹配這樣的架構(gòu):每個(gè)mempool_allocator對(duì)象有一個(gè)成員變 量以指明它綁定在哪個(gè)池上,而mempool_allocator::allocate()將調(diào)用mempool_Alloc()從相應(yīng)的池中獲取內(nèi)存。 [注1]

    最 后,我們到了allocator的定義體中一個(gè)微妙的部份:在不同的類型之間映射。問(wèn)題是,一個(gè)allocator類,比如 malloc_allocator<int>,全部是圍繞著單個(gè)value_type構(gòu)建 的:malloc_allocator<int>::pointer是int*,malloc_allocator<int> ().allocate(1)返回足夠容納一個(gè)int對(duì)象的內(nèi)存,等等。然而,通常,容器類使用malloc_allocator可能必須處理超過(guò)一個(gè)類 型。比如,一個(gè)list類,不分配int對(duì)象;實(shí)際上,它分配list node對(duì)象。(我們將在下一段落研究細(xì)節(jié)。)于是,當(dāng)你創(chuàng)建一個(gè)std::list<int, malloc_allocator<int> >時(shí),list必須將malloc_allocator<int>轉(zhuǎn)變成為處理list_node類型的 malloc_allocator。

    這 個(gè)機(jī)制稱為重綁定,它有二個(gè)部份。首先,對(duì)于給定的一個(gè)value_type是X1的allocator類型A1,你必須能夠?qū)懗鲆粋€(gè)allocator 類型A2,它與A1完全相同,除了value_type是X2。其次,對(duì)于給定的A1類型的對(duì)象a1,你必須能夠創(chuàng)建一個(gè)等價(jià)的A2類型對(duì)象a2。這兩部 分都使用了成員模板,這也就是allocator不能被老的編譯器支持,或支持得很差的原因。