空間配置器allocator

先解決一個坑 allocator

在源碼剖析的第二章,便是講的allocator,(第一次看的時候一臉蒙B)

因爲所有的STL的操作對象都存放在容器內,而容器一定要配置空間以置放資料,都要用到allocator


SGI標準的空間適配器 std::allocator

下面的代碼,只是把C++的:::operator new::operator delete 做一層薄薄的包裝,SGI 從未使用過它,也不建議我們使用,主要原因是效率不佳

//這個程序在CODEBLOCKS上運行失敗,有很多錯誤

#ifndef DEFALLOC_H
#endif  DEFALLOC_H  

#include <new.h>      //for placement new
#include <stddef.h>  //for ptrdiff_t, size_t
#include <stdlib.h>  //for exit()
#include <iostream.h> //for cerr
#include <algobase.h>

    template<class T>
    inline T* allocate(ptrdiff_t size, T*)
    {
        set_new_handler(0);
        T* tmp = (T*)(::operator new((size_t)(size * sizeof(T)));
        if(tmp == 0)
        {
            cerr<<"out of memory" <<endl;
            exit(1);
        }

        return tmp;
    }

    template<class T>
    inline void deallocate(T* buffer)
    {
        ::operator delete(buffer);
    }
    //_________________________________

    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;

        pointer allocate(size_type n)
        {
            return  ::allocate((difference_type)n,(pointer)0)   //(pointer)0 ?????????
        }

        void deallocate(pointer p)
        {::deallocate(p);}

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

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

      size_type init_page_size() 
        {
            return max(size_type(1),size_type(4096/sizeof(T));       
        }


        size_type max_size() const
        {
            return max(size_type(1),size_type(UINT_MAX/sizeof(T));
            //UINT_MAX沒有初值啊?
        }
    };


#endif

SGI特殊的空間配置器 std::alloc

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

new內含倆階段操作:
1.調用::operator new 配置內存
2.調用Foo::Foo() 構造對象內容
delete也是倆階段操作:
1.調用Foo::~Foo() 將對象析構
2.調用 ::operator delete 釋放內存

爲了精密分工,STL allocator決定將這倆階段操作區分開來
內存配置操作由alloc:allocator() 負責
內存釋放操作由alloc:deallocate() 負責
對象構造操作由 ::construct() 負責
對象析構操作由 ::destroy() 負責

STL allocator的整體結構
STL ALLO的整體結構


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

#include<new.h>  //for placement new

template<class T1, class T2>
inline void construct(T1*p, const T2& value)
{
    new (p) T1(value);      //placement new; 調用 T1::T1(value);
}

//以下是destroy()第一個版本,接受一個指針

template<class T>
inline void destroy(T* pointer)
{
    pointer->~T();          //調用dtor ~T()

}

//以下是destroy()的二個版本,接受倆個迭代器,此函數設法找出元素的數值型別
//進而利用 _type_traits<>求取最適當措施

template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last)
{
    _destroy(first,last,value_type(first));
}

//判斷元素的數值型別(value type)是否有trivial destructor

template<class ForwardIterator , class T>
inline void _destroy(ForwardIterator first, ForwardIterator last, T*)
{
    typedef typename _type_traits<T>::has_trivial_destructor trivial_destructor;
    _destroy_aux(first, last, trivial_destructor());
}

//如果元素的數值型別(value type)有non-trivial destructor...(有意義)

template<class ForwardIterator>
inline void
_destroy_aux (ForwardIterator first, ForwardIterator last, _false_type)
{
    for(; first<last; ++first)
        destroy(&*first);
}

//如果元素的數值型別(value type)有trivial destructor...

template<class ForwardIterator>
inline void 
_destroy_aux (ForwardIterator, ForwardIterator, _true_type) {}  //什麼都不做

//以下是detroy()第二版本針對迭代器爲char* 和 wchar_t* 的特化版

inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}

這幅圖片很好的概括了以上代碼
這裏寫圖片描述

這裏再說一下上面代碼幾個點:
1.trivial destructor
has_trivial_destructor函數
trivial的意思是”不重要的,無意義的“的,所以non-trivial就是”有意義的“
現在膚淺的理解是:如果不定義析構函數,用系統自帶的,則是trivial distructor,什麼都不用做。但如果是non_trivial distructor的話,說明用戶自定義了析構函數(問題來了。那麼不應該直接調用它定義的析構函數嗎?

STL源碼剖析中有這麼一段話:(P53)
第二版本destroy,接受first,last倆個迭代器,將[first, last)範圍內的所有對象析構掉,我們不知道這個範圍多大,萬一很大,而每個對象的析構函數都無關痛癢(所謂trivial destructor),那麼一次次調用這些無關痛癢的析構函數對效率是一種傷害
因此,首先利用 value_type() 獲得迭代器所指對象的型別,再利用 _type_traits<T> 判斷該型別的析構函數是否無關痛癢。若是(_true_type),則什麼也不做就結束;若否(_false_type),這才以循環方式巡防整個範圍,並在循環中每經歷一個對象就調用第一個版本的destroy()

空間的配置與釋放 std::alloc

上面說了對象的構造和析構
然後再說空間(內存)的配置和釋放
內存配置操作由alloc:allocator() 負責
內存釋放操作由alloc:deallocate() 負責

看到很有趣的一短話:

SGI對此的設計哲學如下:

 - 向 system heap 要求空間
 - 考慮多線程 (multi-threads)狀態 (一下內容皆不考慮)
 - 考慮內存不足時的應變措施
 - 考慮過多“小型區塊”可能造成的內存碎片(fragment)問題

接下來的代碼都是根據這幾點設計的

2016.5.4 有時間繼續補吧

發佈了46 篇原創文章 · 獲贊 4 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章