STL學習筆記--2、空間配置器 allocator

2.1標準接口

allocator::value_type
allocator::pointer
allocator::const_pointer
allocator::reference
allocator::const_reference
allocator::size_type
allocator::difference_type
allocator::rebind
allocator::allocator()//默認構造函數
allocator::allocator(const allocator&)//拷貝構造函數
template <class U> allocator::alloctor(const alloctor<U> &)//泛化的拷貝構造
allocator::~allocator()//析構函數
pointer allocator::address(reference x) const //返回某個對象的地址
const_pointer allocator::address(const_reference x) const //返回某個const對象的地址
pointer allocator::allocate(size_type n,const void*=0)//配置空間,存儲n個T對象。第二個參數是提示,可忽略
void allocator::deallocate(pointer p,size_type n)//歸還先前配置的空間
size_type allocator::max_size() const //返回可配置的最大量
void allocator::construct(pointer p,const T&)//構造T對象
void allocator::destroy(pointer p)//對象T的析構

一個簡單的空間配置器 JJ::allocator

#ifndef _JJALLOC_
#define _JJALLOC_

#include <new>
#include <cstddef>
#include <cstdlib>
#include <climits>
#include <iostream>

namespace JJ
{
    // 使用operator new分配空間
    template<class T>
    inline T* _allocate(ptrdiff_t size, T*)
    {
        std::set_new_handler(0);//註釋1
        T *tmp = (T*)(::operator new((size_t)(size * sizeof(T))));//註釋2
        if (tmp == 0)
        {
            std::cerr << "out of memory" << std::endl;
            exit(1);
        }
        return tmp;
    }
    // 使用operator delete回收空間
    template<class T>
    inline void _deallocate(T* buffer)
    {
        ::operator delete(buffer);
    }
    // 在指定內存上構造一個對象
    template<class T1, class T2>
    inline void _construct(T1* p, const T2& value)
    {
        // placement new
        new (p) T1(value);
    }
    // 析構一個對象
    template<class T>
    inline void _destroy(T* ptr)
    {
        ptr->~T();
    }
    // 遵循allocator的標準定義相關結構
    template<class T>
    class allocator
    {
    public:
        typedef T           value_type;
        typedef T*          pointer;
        typedef const T*    const_pointer;
        typedef T&          reference;
        typedef const T&    const_reference;
        typedef size_t      size_type;
        typedef ptrdiff_t   difference_type;

        template<class U>
        struct rebind
        {
            typedef allocator<U> other;
        };

        pointer allocate(size_type n, const void* hint=0)
        {
            return _allocate((difference_type)n, (pointer)0);
        }

        void deallocate(pointer p, size_type n)
        {
            _deallocate(p);
        }

        void construct(pointer p, const T& value)
        {
            _construct(p, value);
        }

        void destroy(pointer p)
        {
            _destroy(p);
        }

        pointer address(reference x)
        {
            return (pointer)&x;
        }

        const_pointer const_address(const_reference x)
        {
            return (const_pointer)&x;
        }

        size_type max_size() const
        {
            return size_type(UINT_MAX/sizeof(T));
        }
    };
}

#endif

註釋1:

typedef void (*new_handler)();
new_handler set_new_handler(new_handler new_p) throw();

使用set_new_handler可以設置一個函數new_p,當使用new/operator new分配內存失敗時,new_p將被調用。new_p將嘗試使得更多內存空間可用,以使得接下來的內存分配操作能夠成功。如果new_p指向NULL(默認就是NULL),那麼將會拋出bad_alloc異常,這也是爲什麼我們默認使用new失敗的時候將會拋出bad_alloc異常的原因;

註釋2:

我們使用的new叫做new operator,包括兩個步驟:
一是調用operator new來分配指定大小的內存空間
二是調用構造函數;
所以如果只是進行空間分配操作,那麼使用operator new就可以了,就好比C的malloc函數;
如果已經分配好了空間,想在上面構造一個對象,那麼可以使用placement new,上面的_construct函數裏面調用的就是placement new;


2.2具備次配置能力的SGI空間配置器

SGI的空間配置器名稱爲alloc,而非allocator,不接受任何參數。

vector<int,allocator<int>> iv;
vector<int,alloc> iv;

我們所習慣的c++內存配置操作和釋放操作如下:

class Foo { ... };
Foo* pf = new Foo; // 配置內存,然後構造對象
delete pf; // 將對象析構,然後釋放內存

這其中的new 操作符(new operator)包含兩階段操作:

1)調用operator new配置內存
(2)調用Foo::Foo( )構造函數構造對象內容。

delete操作符包含兩階段操作:

1)調用Foo::~Foo( )析構函數將對象析構。
(2)調用operator delete釋放內存

STL allocator 將這兩階段操作區分開來。
內存配置操作由 alloc::allocate() 負責,內存釋放操作由 alloc::deallocate() 負責;
對象構造操作由 ::construct() 負責,對象析構操作由 ::destroy() 負責。

配置器定義在<memory>中,SGI中

//負責內存空間的配置與釋放;
<stl_alloc.h>//文件中定義了一、二兩級配置器,彼此合作,配置器名爲alloc。

//負責對象內容的配置與釋放
<stl_construct.h>//全局函數construct()和destroy(),負責對象的構造和析構。

//用來填充fill或複製copy大塊內存數據
<stl_uninitialized.h>//uninitialized_copy();uninitialized_fill();uninitialized_fill_n
uninitialized_copy(first, last, result) //將[first,last)範圍內的對象複製到result處;
uninitiated_fill(first, last, X) //將[first,last)範圍內的內存用對象X的副本填充;
uninitiated_fill_n(first, n, X) //將first開始的n個連續的內存空間用X的副本填充;

1、構造和析構基本工具:construct()和destroy()

#include <stl_construct.h>

construct()接受一個指針p和初值value,該函數將初值設定到所指空間上;
destroy()有兩個版本:

1、接收一個指針,準備將該指針所指之物析構掉。直接調用該對象的析構函數;

2、接收first和last兩個迭代器,準備將[first,last)範圍內的所有對象析構掉。
利用value_type()獲取迭代器所指對象的類型,利用__type_traits<T>判斷該類型的析構函數是否無關痛癢。
若是__true_type,則什麼也不做,反之若是__false_type則循環訪問,對每個對象調用析構函數。

2、空間的配置和釋放:std::alloc

#include <stl_alloc.h>

改進:

1.通過內存池技術提升了分配效率:
2.對小內存頻繁分配所可能造成的內存碎片問題的處理
3.對內存不足時的處理
SGI STL在<stl_alloc.h>中定義了兩級配置器。
第一級空間配置器使用malloc/free函數,當分配的空間大小超過128 bytes的時候使用第一級空間配置器;第一級配置器`__malloc_alloc_template`
第二級空間配置器使用了內存池技術,當分配的空間大小小於128 bytes的時候,將使用第二級空間配置器。第二級配置器`__default_alloc_template`

這裏寫圖片描述

3、第一級配置器__malloc_alloc_template

以malloc(),free(),realloc()等C函數執行實際的內存配置、釋放、重配置操作,並出現在類似於C++ new-handle機制。
C++ new-handle機制:要求系統在內存配置無法滿足要求時,調用自己制定的函數。

4、第二級配置器__default_alloc_template

避免造成太多小額區塊的內存碎片。
SGI第二級配置器主動將小額區塊的內存需求上調至8的倍數。

SGI STL的第二級內存配置器維護了一個free-list數組用於管理:
8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes的小額區塊。
free-list的節點結構如下:

union obj
{
    union obj* free_list_link;
    char client_data[1];
};

5、空間配置函數allocate()

此函數首先判斷區塊大小,大於128調用__malloc_alloc_template,小於128檢查對應的free-list。

 /* n must be > 0      */
static void * allocate(size_t n)
{
    obj * __VOLATILE * my_free_list;
    obj * __RESTRICT result;

    //1、大於128字節就調用第一級配置器
    if (n > (size_t) __MAX_BYTES) 
    {
        return(malloc_alloc::allocate(n));
    }

    //尋找16個鏈表中適當的
    my_free_list = free_list + FREELIST_INDEX(n);
    //2、有可用區塊
    result = *my_free_list;
    //3、鏈表沒有可用的塊調用refill申請
    if (result == 0)
    {
        void *r = refill(ROUND_UP(n));
        return r;
    }
    //調整鏈表不再指向這塊被使用的塊
    *my_free_list = result -> free_list_link;
    return (result);
};

6、空間釋放函數:deallocte()

/* p may not be 0 */
static void deallocate(void *p, size_t n)
{
    obj *q = (obj *)p;
    obj * __VOLATILE * my_free_list;

    //大於128使用第一級配置器
    if (n > (size_t) __MAX_BYTES) 
    {
        malloc_alloc::deallocate(p, n);
        return;
    }
    //找到所屬鏈表
    my_free_list = free_list + FREELIST_INDEX(n);

    //調整鏈表回收區塊
    q -> free_list_link = *my_free_list;
    //鏈表重新指向回收塊
    *my_free_list = q;

}

7、重新填充free-list:refill()

新的空間取自內存池(調用chunk_alloc())。缺省取20個新區塊,但萬一內存池不夠,獲得的新區快將少於20。

void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    //默認申請塊數
    int nobjs = 20;
    //nobjs爲傳引用
    char * chunk = chunk_alloc(n, nobjs);
    obj * __VOLATILE * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;

    //只有一塊返回給調用者鏈表無新節點
    if (1 == nobjs) 
        return(chunk);

    //不止一塊找到所屬鏈表
    my_free_list = free_list + FREELIST_INDEX(n);

    //對新的區塊建鏈表
    result = (obj *)chunk;
    //鏈表指向從內存池拿出的鏈表
    *my_free_list = next_obj = (obj *)(chunk + n);
    //將新鏈表之間的節點串接起來
    for (i = 1; ; i++)  //從開始第個返回給申請者
    {
        //當前節點
        current_obj = next_obj;
        //指向下一個節點
        next_obj = (obj *)((char *)next_obj + n);
        if (nobjs - 1 == i) 
        {
            //尾節點置
            current_obj -> free_list_link = 0;
            break;
        } 
        else 
        {
            //當前節點指向下一個節點
            current_obj -> free_list_link = next_obj;
        }
    }
    return(result);   //返回給申請者
}

8、內存池

chunk_alloc()工作原理:

1、 內存池有足夠大小的空間,則分配申請的空間;
2、 內存池沒有足夠大小的空間,但是至少還能分配一個節點的空間,則能分多少分多少;
3、 內存池一個節點都騰不出來,向系統的heap**申請2倍於要求大小的空間**,在此之間,如果內存池剩餘有空間,則放到free-list中去;
4、 配置heap空間,補充內存池。如果向heap申請空間失敗,那麼只能看free-list中更大的節點是否有可用空間了,有則用之,同時遞歸調用自身修正chunk_alloc(size,__nobjs)
5、 如果free-list也沒有可用節點了,那麼轉向第一級空間配置器申請空間;
6、 再不行,第一級空間配置器就拋出bad_alloc異常。


2.3內存基本處理工具

1、uninitialized_copy

template<class InputIterator,class ForwardIterator>
ForwardIterator uninitialized_copy(InputIterator first,InputIterator last,ForwardIterator result)

作爲輸出目的地的[result,result+(last-first))範圍內的每個迭代器都指向未初始化區域,則uninitialized_copy()調用copy construct,將[first,last)範圍內的每個對象的複製品放入輸出範圍。

有兩個特化版本,分別爲char*,wchar_t*

2、uninitialized_fill

template<class ForwardIterator,class T>
void uninitialized_fill(ForwardIterator first,ForwardIterator last,const T& x)

作爲輸出目的地的[first,last)範圍內的每個迭代器都指向未初始化區域,則uninitialized_fill在該範圍內產生x的複製品。

3、uninitialized_fill_n

template<class ForwardIterator,class size,class T>
ForwardIterator uninitialized_fill_n(ForwardIterator first,size n,const T& x)

作爲輸出目的地的[first,first+n)範圍內的每個迭代器都指向未初始化區域,則uninitialized_fill_n在該範圍內產生x的複製品。

在實現代碼中,若迭代器first的value_type爲POD(Plain Old Data也即是標量型別)型別,則對POD採取高階函數執行,對non-POD採取保險的安全做法。

參考:
http://blog.csdn.net/will130/article/details/51315647
http://www.cnblogs.com/guyan/archive/2012/09/10/2678606.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章