STL的操作对象(所有的数值)都存放在容器之内, 而容器一定需要配置空间以置放资料,所以这时需要到空间适配器。
1.空间配置器的标准接口
根据STL的规范,以下是allocator的必要接口:
allocator: :value_type
allocator: :pointer
allocator::const_pointer
allocator::reference
allocator::const_reference
allocator::size_type
allocator::difference_type
allocator: :rebind
allocator: : allocator ()
default constructor
allocator::allocator(const allocator&)
copy constructor
template <class U>allocator: :allocator(const allocator<U>&)
泛化的copy constructor allocaLor:: ~allocator ()
default constructor
pointer allocator::address(reference x) const
返回某个对象的地址。算式a.address(x)等同于&x
const_pointer allocator::address(const_reference x) const
返回某个const对象的地址。算式a.address(x)等同与&x
pointer allocator::allocate(size_type n, cosnt void*= 0)
配置空间,足以存储n个 T 对象.第二参数是个提示。实现上可能会利用它来增进区域性(locality) , 或完全忽略之
void allocator: :deallocate(pointer p, size_type n)
归还先前配置的空间
size_type allocator::max_size().const
返回可成功配置的最大量
void allocator: :construct(pointer p, const T& x)
等同于new(const void*) p) T(x)
void allocator: :destroy(pointer p)
等同于p->~T()
1.1 简单的空间配置器,JJ::allocator
#include<new>
#include<cstddef>
#include<cstdlib>
#include<climits>
#include<iostream>
#include<vector>
using namespace std;
namespace JJ
{
template <class T>
inline T* _allocate(ptrdiff_t size, T*)
{
set_new_handler(0);
T* tmp = (T*)(::new_handler 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 T1,class T2>
inline void _deallocate(T* buffer) {
::operator delete(buffer);
}
template <class T1,class T2>
inline void _construct(T1* p, const T2& value) {
new(p) T1(value);
}
template <class>
inline allocator(T* ptr) {
ptr->~T();
}
template <class T>
class allocator {
public:
typedef T
typedef T*
typedef const T*
typedef T&
typedef const T&
typedef size_t
typedef ptrdiff_t
typedef <class U>
struct rebind {
typedef allocator<U> other;
};
pointer allocator(size_type n, const coid* 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));
}
};
}
int main()
{
int ia[5] = { 0,1,2,3,4 };
unsigned int i;
vector<int, JJ::allocator<int> >iv(ia, ia + 5);
for (i = 0; i < iv.size(); i++)
cout << iv[i] << ' ' << endl;
}
2.具备次配置力(sub-allocation)的SGI空间配置器
SGI STL的配詈器与众不同, 也与标准规范不同, 其名称是alloc而非allocator, 而且不接受任何参数。换句话说,如果你要在程序中明白采用SGI配置器,则不能采用标准写法:
vector<int,std::allocator<int> >iv;
必须这样写:
vector<int.std::alloc> iv;
SGI STL allocator未能符合标准规格,不会带来困扰,因为通常我们使用缺省的空间配置器,很少需要自行指定配置器名称,而SGISTL的 每一个容器都已经指定其缺省的空间配置器为alloc。 例如下面的vector声明:
template <class T, class Alloc=alloc>
2.1 SGI标准的空间配宣器, std ::allocator
SGI也定义有一个符合部分标准、 名为allocator的配置器,不建议使用。 主要原因是效率低, 只把C++的::operator new和::operator delete做一层薄薄的包装而已。下面是SGI的std::allocator全貌:
G++ 2.91.57, cygnus\cygwin-b20\include\g++\defalloc.h完整列表
#include <new.h>
#include <stddef.h>
#include <s七dlib.h>
#include <limi七s.h>
#include <ios七ream.h>
#include <algobase.h>
template <class T>
inline T* allocate(ptrdiff_t size, T*) {
set_new—handler(O);
T* tmp = (T*) (: :opera七or 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);
}
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(l), size_type(4096/sizeof(T)));
}
size _typ e max_s1.ze() const {
return max(size_type(l), size_type(UINT_MAX/sizeof(T)));
}
};
class allocator<void> {
public:
typedef void* pointer;
};
2.2 SGI特殊的空间配宣器, std ::alloc
上面的allocator只是基层内存配置/释放行为(也就是::operator new和::operator delete)的 层薄薄的包装, 并没有考虑到任何效率上的强化,SGI有其他方法供其本身使用。
一般C++内存配置操作和释放方式:
class Foo{...};
Foo* pf = new Foo;
delete pf;
new含两阶段操作:
- 调用:: operator new配置内存;
- 调用Foo::Foo ()构造对象内容。
delete含两阶段操作:
- 调用Foo: :-Foo() 将对象析构;
- 调用: :operator delete释放内存。
STL allocator将这两阶段操作区分开来。内存配置操 作由alloc:allocate() 负责,内存释放操作由alloc: :deallocate() 负责; 对象构造操作由: : construct () 负责,对象析构操作由: : destroy () 负责。STL标准指出 配置器定义于 之中,SGI 内 含以下两个文件:
#include <stl_alloc.h>
#include <stl_construct.h>
内存空间的配置/释放与对象内容的构造/析构由有这两个文件定义,<stl_construct.h>定义有两个基本函数:构造用的construct()和析构用的destroy ()。在复杂的内存动态配置与释放之前,让我们先看清楚这两个函数如何完成对象的构造和析构。:
2.3 构造和析构基本工具: construct()和destroy()
#include <new.h>
template <class Tl, class T2>
inline void const ruet(Tl* p, const T2& value) {
new (p) Tl (value);
}
inline void destroy(T* pointer) {
pointer->~T () ;I
}
template <class ForwardIterator>
inline void destroy(ForwardIterator first,ForwardIterator last) {
__destroy(first, last, value_type(first));
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());
}
template <class ForwardIterator>
inline void
_destroy_aux(Forwarditerator first, ForwardIterator last, _false_type) {
for (; first < last; ++first)
destroy(&*first);
}
template <class ForwardIterator>
inline void destroy—aux(Forwarditerator, Forwarditerator, _true_type) {}
inlinf" void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t *) {}
- 作为构造 、 析构的函数被设计为全局函数,STL还规定配置器必须拥有名为construct ()和destroy ()的两个成员 函数, 然而真正在SGI STL中大显身手的那个名为std: :alloc的配置器并未遵守这一 规则。
- construct ()接受一个指针p和一个初值value, 该函数的用途就是将初值设定到指针所指的空间上
destroy ()有两个版本:
- 第一版本接受一个指针,准备将该指针所指之物析构掉。 这很简单,直接调用该对象的析构函数即可
- 第二版本接受frist和last 两个迭代器,准备将[first, last )范围内的所有对象析构掉。不确定析构范围,可能很大,而每个对象的析构函数都无关痛痒,那一次次调用是种伤害。因此,首页利用value_type()获得迭代器所指对象类型,在利用_type_traits判断该类型析构函数是否无关痛痒。若是(_true_type),就结束;若为(_false_type),循环查看整个范围,循环一个对象调用destroy()。
2.4 空间的配宣与释放, std::alloc
了解内存配置后的对象构造行为和内存释放前的对象析构行为,现在 看看内存的配置和释放。 对象构造前的空间配置和对象析构后的空间释放, 由<S七 l_alloc.h>负责, SGI对此的设计哲学如下:
- 向systemheap要求空间。
- 考虑多线程(multi-threads)状态。
- 考虑内存不足时的应变措施。
- 考虑过多 “小型区块可能造成的内存碎片(fragment)问题。
C++的内存配置基本操作:: operator new(), 内存释放基本操作是:: operator delete()。 这两个全局函数相当于C的malloc ()和free()函 数 是的, 正是如此,SGI正是以malloc ()和free()完成内存的配置与释放。
考虑到小型区块所可能造成的内存破碎问题,SGI设计了双层级配置器:
- 第一级配置器直接使用malloc()和free()。
- 第二级配置器则视情况采用不同的策略: 配置区块超过128 bytes,用第一级配置器;当小于128 bytes,用复杂的memory pool整理方式,不再求助于第一级配置器。
- 整个设计究竟只开放第一级配置器, 或是同时开放第二级配置器, 取决于_USE_MALLOC6 是否被定义( 我们可以轻易测试出来,SGI STL并未定义_USE_MALLOC)。
# ifdef _USE_MALLOC
...
typedef _malloc_alloc_template<O> malloc—alloc;
typedef malloc_alloc alloc;
# else
...
typedef _default_alloc_template<_NODE_ALLOCATOR_THREADS, 0> alloc;
#endif
- 其中_malloc_alloc_template就是第 一 级配置器,
- default_alloc template就是第二级配置器。
- alloc并 不接受任何template型别参数。
无论alloc被定义为第一级或第二级配置器, SGI还为它再包装一个接口如下, 使配置器的接口能够符合STL规格:
template<class T, class Alloc>
class simple_alloc (
public:
static T *allocate(size_t n)
{return 0 == n?0 : (T*)Alloc::allocate{n*sizeof(T));
static T *allocate(void)
{return (T*) Alloc::allocate(n * sizeof(T));
static void deallocate(T *p,size_t n)
{if(0 !=n) Alloc::deallocate(p,n*sizeof(T));
staitc void deallocate(T *p)
{Alloc::deallocate(p,sizeof (T));}
其内部四个成员函数其实都是单纯的转调用,调用传递给配置器(可能是第一级也可能是第二级)的成员函数。 这个接口使配置器的配置单位从bytes转为个别元素 的大小(sizeof (T))。 SGISTL容器全都使用这个simple_alloc接口、 例如:
template<class T, class Alloc = alloc>
class vector {
protected:
typedef simple_alloc<value_type,Alloc>data_allocator;
void deallocate(){
if(...)
data_allocator::deallocate(start,end_of_storage - start);
}
...
};
一、 二级配置器的关系, 接口包装, 及实际运用方式: 2.5 第一级配宣器__m alloc_alloc_tem plate剖析
#if 0
# include<new>
# define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
# include <iostream.h>
# define __THROW_BAD_ALLOC cerr<<"out memory"<<endl;
exit(1)
#endif
template <int inst>
class _malloc_alloc—template {
private:
static void *oom_malloc(size_t);
static void *oom_realloc(void * size_t);
static void (* __malloc_alloc_oom_handler)();
public:
static void* allocate(size_t n)
{
void *result = malloc(n);
if (0 == result) result= oom_malloc(n);
return result;
}
static void deallocate (void *p, size_t )
{
free(p);
}
static void* reallocate (void *p, size_t , size_t new_sz)
{
void* result= realloc(p, new—sz);
if (0 == result) result=oom_realloc(p, new_sz);
return result;
}
static void{* set_malloc_handler{void {*f)()))()
{
void { * old) {) = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return (old);
}
};
template <int,inst>
void (* malloc_alloc_template<inst>::_malloc_alloc_oom_handler) ()=O;
template <int inst>
void* _malloc_alloc_template<inst>: :oom_malloc(size_t n)
{
void (* my_malloc_handler) ();
void *result;
for (;;) {
my_malloc—handler= _malloc_alloc_oom_handler;
if (0 == my—malloc—handler) { _THROW—BAD—ALLOC; }
(*my _malloc_handler) () ;
result = malloc (n);
if (result) return(result);
}
}
template <int inst>
void*__malloc_alloc_template<inst>::oom_realloc(void *p,size_t n)
{
void (* my—rnalloc_handler)();
void *result;
for (;;) {
my_malloc_handler = _malloc_alloc_oom_handler;
if(0 == my_malloc_handler) {__THROW_BAD_ALLOC; }
(*my_malloc_handler)();
result = realloc (p, n);
if (result) return(result);
}
}
typedef _malloc_alloc_template<O>malloc_alloc;
-
第一级配置器以malloc() , free () , realloc ()等C函数执行实际的内存 配置、释放、重配置操作,并实现出类似C++new-handler的机制。不能直接运用C++new-handler机制,因为它并非使用::operator new来配置内存。 -
C++new handler机制指可以要求系统在内存配置需求无法被满足时,调用一个你所指定的函数。换句话说,一旦:;operator new无法完成任务, 在丢出std::bad_alloc异常状态之前,会先调用由客端指定的处理例程。该处理例程通常即被称为new-handler。new-handler解决内存不足的做法有特定的模式。
注意:
- SGI以malloc而非::operator new来配置内存( 一个原因是历史因素,另一个原因是C++并未提供相应于realloc()的内存配 置操作),因此,SGI不能直接使用C++的set_new_handler(),必须仿真一个类似的set_malloc_handler()。
- SGI第一级配置器的allocate()和realloc()都是在调用malloc ()和realloc()不成功后,改调用oom_malloc()和oom_realloc()。后两者都有内循环,不断调用“内存不足处理例程”,期望在某次调用之后,获得足够的内存完成任务。但如果“内存不足处理例程”并未被客端设定,并未被客端设定, oom_malloc ()和oom_realloc()便老实地调用__THROW_BAD_ALLOC,丢出bad_alloc异常信息,或利用exit(1)终止程序。
- 设计“内存不足处理例程”是客端的责任,设定“内存不足处理例程”也是客端的责任。“内存不足处理例程”解决问题的做法有特定的模式。
2.6 第二级配宣器__default_alloc_ ternplate剖析
第二级配置器多了一些机制,避免太多小额区块造成内存的碎片。小额区块带来不仅是内存碎片,配置时的额外负担(overhead)也是一个大问题,额外负担永远无法避免,毕竟系统要靠这多出来的空间来管理内存,如图所示。但是区块愈小,额外负担所占的比例就愈大,愈显得浪费。
SGI第二级配置器的做法是,
- 区块超过128 bytes, 就移交第一级配置器处理。
- 当区块小于128 bytes, 则以内存池(memory pool)管理, 此法 又称为次层配置(sub-allocation) : 每次配置一大块内存, 并维护对应之自由链表(free-list)。 若再有相同大小的内存需求,就直接从free-lists中拨出。
- 客端释还小额区块,就由配置器回收到free-lists中, 且配置器除了负责配置, 也负责回收。
- SGI第二级配置器可以主动将任何小额区块的 内存需求量上调至8的倍数(例如客端要求30 bytes, 就自动调整为32 bytes) 并维护16个free-lists, 各自管理大小分别为8, 16, 24, 32,40,48,56,64, 72, 80, 88, 96, 104, 112, 120, 128 bytes的小额区块。
free-lists的节点结构如下:
union obj {
union obj* free_list_link;
char client_data[1];
};
-
为了维护链表(lists) , 每个节点需要额外的指针(指向下一 个节点), 会造成额外负担。但已经有了解决办 法。 -
上述obj所用的是union, 由于union之故,从其第一字段观之, obj可被视为一个指针,指向相同形式的另 一个obj;从其第二字段观之, obj可 被视为一个指针, 指向实际区块,如图所示。 -
一物二用的结果是, 不会为了维 护链表所必须的指针而造成内存的另一种浪费(我们正在努力节省内存的开销呢)。 这种技巧在强型(strongly typed)语言如Java中行不通,但是在非强型语言如C++ 中十分普遍。 下面是第二级配置器的部分实现内容:
enum {_ALIGN 8};
enum {_MAX_BYTES 128};
enum {_NFREELISTS =_MAX_BYTES/_ALGN};
template <bool threads, int inst>
class _default_alloc_template {
private:
static size_t ROUND_UP(size_t bytes) {
return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
}
private:
union obj{
union obj * free_list_link;
char client_data[1];
};
private:
static obj * volatile free_list[__NFREELISTS];
static size_t FREELIST_INDEX(size_t bytes){
return (((bytes) + __ALIGN-1)/__ALIGN -1);
}
static void *refill(size—t n);
static char *chunk_alloc(size_T size,int &nobjs);
state static char *start_free;
static char *end_free;
static size_t heap_size;
public:
static void* allocate(size_t n) { )
static void deallocate(void *p, size_t n) { }
static void* reallocate(void *p , size_t old_sz, size_t new_sz);
};
template <bool threads,int inst>
char *__default_alloc_template<threads,inst>::start_free = 0;
template <bool threads,int inst>
char *__default_alloc_template<threads,inst>::end_free = 0;
template <bool threads,int inst>
size_t __default_alloc_template<threads,ints>::heap_size = 0;
template <bool threads,int inst>
__default_alloc_template<threads,ints>::obj * volatile
__default_alloc_template<threads,inst>::free_list[__NFREELISTS]={
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
2.7 空间配宣函数allocate()
- 身为一个配置器,__default_alloc_template拥有配置器的标准接口函数 allocate ()。
- 此函数首先判断区块大小,大于128bytes就调用第一级配置器;小 于128bytes就检查对应的freelist。
- 如果freelist之内有可用的区块,就直接拿来 用,如果没有可用区块,就将区块大小上调至8倍数边界,然后调用refill(), 准备为free list重新填充空间
static void* allocate(size_t n)
{
obj *volatile* my_free_list;
obj * result;
if (n > (size_t)__MAX_BYTES) {
return (malloc_alloc::allocate(n));
}
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
if (result == 0) {
void* r = refill(ROUND_UP(n));
return r;
}
*my_free_list = result->free_list_link;
return (result);
};
区块自free list调出的操作:
2.8 空间释放函数deallocate()
身为一个配置器,__default_alloc_ternplate拥有配置器标准接口函数deallocate ()。该函数首先判断区块大小,大与128bytes就调用第一级配置器,小与128 bytes就找出对应的free list, 将区块回收。
sta七 ic void deallocate(void *p, size_t n)
{
bj *q = (obj*)p;
obj *volatile* rny_free_list;
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;
}
区块回收纳入free list的操作: 2.9 重新填充free lists
前面讨论说过的allocate()。当它发现free list中没有可用区块了时,就调用refill (), 准备为free list重新填充空间。新的空间将取自内存池(经由chunk_alloc () 完成)。缺省取得20个新节点(新区块),但万一内存池不足,获得的节点数(区块数)可能小与20:
template<bool threads, int inst>
void* _default_alloc_template<threads, inst>::refill(size_t n)
{
int nobjs = 20;
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 == 1.) {
current_obj-> free_list_link = O;
break;
} else {
current_obj -> free_list_link = next_obj;
}
}
return (result)
}
2.10 内存池(memorypool)
从内存池中取空间给freelist使用,是chunk_alloc()的工作:
template<bool threads, int inst>
char*
_default_alloc—template<threads, inst>::
chunk_alloc(size_t size,int&nobjs)
{
char* result;
size_t total_bytes=size* nobjs;
size_t bytes_left= end_free-start_free;
if(bytes_left >= total_bytes){
result= start_free;
start_free += total—bytes;
return(result);
}
else if (bytes_left >= size} {
nobjs = bytes_left/size;
total_bytes =size* nobjs;
result= start_free;
start_free+= total_bytes;
return(result);
}else{
size_t bytes_to_get = 2 * total_bytes + ROUND_UP (heap_size >> 4} ;
if (bytes_left > 0} {
obj * vola口le*my_free_list =
free_list + FREELIST_INDEX(bytes_left);
((obj *)start_free) -> free_list_link = *my_free_list;
*my_free_list = (obj *)start_free;
}
start_free =(char*)malloc(bytes_to_get);
if (0 == start_free} {
II heap空间不足,malloc(}失败
1.nt 1.;
obj *volatile * my_free—list, *p;
for (i = size; i <=__MAX_BYTES; i +=__ALIGN) {
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;
if(0 !=p){
*my_free_list = p -> free_list_link ; start_free = (char *}p;
end_free = start_free + 1.;
return(chunk_alloc(size, nobj s });
}
}
end_free = O;
start_free= (char *)malloc_alloc::allocate(bytes_to_get);
}
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
return(chunk_alloc(size, nobjs));
}
}
上述的chunk_alloc ()函数以end_free - start_free来判断内存池的水量。
- 如果水量充足, 就直接调出20个区块返回给free list。
- 如果水量不足以提供20 个区块, 但还足够供应一个以上的区块, 就拨出这不足20个区块的空间出去。 这时候其pass by reference的nobjs参数将被修改为实际能够供应的区块数。
- 如果内存池连一个区块空间都无法供应,对客端显然无法交待,此时便需利用malloc () 从heap中配置内存, 为内存池注入活水源头以应付需求。 新水量的大小为需求量 的两倍, 再加上一个随着配置次数增加而愈来愈大的附加量。
例如:如图所示:整个第二级空间配置器的设计
- 开始,客端就调用chunk_alloc (32, 20), 于是malloc ()配置40个32 bytes区块, 其中第1个交出, 另19个交给 free_list(3] 维护 ,余20 个留给内存池 。
- 接下来客端调用chunk_alloc(64,20), 此时free_list[7]空空如也, 必须向内存池要求支持。 内存池只够供应(32*20)/64=10个64 bytes区块, 就把这10个区块返回, 第1个交给客端, 余9个由free_list[7]维护。 此时内存池全空。
- 接下来再调用 chunk_alloc(96, 20), 此时free_lis七 [11]空空如也, 必须向内存池要求支持, 而内存池此时也是空的,于是以malloc()配置40+n (附加量)个96 bytes区块, 其中第l个交出, 另19个交给free_list[11]维护, 余20+n (附加量)个区块 留给内存池…
- 如果整个systemheap空间都不够了, malloc ()行动失败,chunk_alloc ()就四处寻找有无 “尚有未用区块, 且区块够大” 之free lists。 找到了就挖一块交出, 找不到就调用第一级配置器。 第 一级配置器其实也是使用malloc()来配置内存,但它有out-of-memory处理机制(类似new-handler机制),或许有机会释放其它的内存拿来此处使用。 如果可以, 就成功, 否则发出bad_alloc异常。
前面2.4提到配置器标准接口的simple_alloc:
template <class T,class Alloc>
class simple_alloc{
...
};
SGI 容器通常以这种方式来使用配置器:
template <class T,class Alloc = alloc>
class vector{
public:
typedef T value_type;
...
protected:
typedef simple_alloc<value_type, Alloc> data_allocator;
...
};
其中第二个template参数所接受的缺省参数alloc, 可以是第一级配置器, 也可以是第二级配置器。 不过,SGI STL已经把它设为第二级配置器。
3.内存基本处理工具
STL定义有五个全局函数,作用于未初始化空间上。这样的功能对于容器的实现很有帮助,我们会在第4章容器实现代码中,看到它们肩负的重任。
- 前两个函数是 2.3节说过的用于构造的construct ()和用于析构的destroy(),
- 另三个函数是 uninitialized_copy() ,uninitialized_fill (), uninitialized_fill_n () ,
分别对应与高层次函数copy()、 fill()、 fill_n(),这些都是STL算法, 将在第6章介绍。 - 使用本节的三个低层次函数, 应该包含, 不过 SGI把它们实际定义于<stl_uninitialized>。
3.1 uninitialized_copy
template<class Inputiterator, class Forwarditerator>
ForwardIterator
uninitia1ised_ocpy(InputIterator first, InputIterator last,
ForwardIterator result);
- uninitialized_copy ()够将内存的配置与对象的构造行为分离开来。
- 如果作为输出目的地的[result, result+(last-first))范围内的每 一个迭代器都指向未初始化区域,则uninitialized_copy()会使用copy constructor , 给身为输入来源之[first,last)范围内的每一个对象产生一份复制品,放进输出 范围中。 换句话说, 针对输入范围内的每 一 个迭代器i, 该函数会调用 construct(&*(result+(i-first)),*i), 产生i的复制品, 放置于输出范围的相对位置上。式中的construct()前面提过。
如果需要实现一个容器,uninitial ized_copy ()这样的函数会为你带来很大的帮助,因为容器的全区间构造函数(rangeconstructor)通常以两个步骤完成:
-
配置内存区块,足以包含范围内的所有元素。 -
使用uninitialized_copy(),在该内存区块上构造元素。
C++标准要求uninitalized_copy()具有"commit or rollback"语意,意思是要么“构造出所有必要元素”,要么(当有任何一个copy constructor失败时)”不构造任何东西"。
3.2 uninitialized fill
template <class ForwardJ;terator, class T>
void uninitialized_fill (Forwarditerator first,Forwardlteratorlast,const T& x);
- uninitialized_fill ()也能够使我们将内存配置与对象的构造行为分离开来。
- 如果[first,last)范围内的每个迭代器都指向未初始化的内存,那么
uninitialized_fill()会在该范围内产生X(上式第三参数)的复制品。换旬话说uninitialized_fill ()会针对操作范围内的每个迭代器i,调用 construct (&*i, x), 在l所指之处产生x的复制品。construct()前面讨论过。 - 与uninitialized_copy()一样,uninitialized_fill ()必须具备"commit or rollback"语意,换句话说,它要么产生出所有必要元素,要么不产生任何元素。 如果有任何一个copy constructor丢出异常(exception),uninitialized_fill (),必须能够将已产生的所有元素析构掉。
3.3 uninitialized_fill_n
template <class Forwarditerator, class Size, class T>
Forwarditerator
uninitialized fill_n(Forward工teratorfirst,Sizen, const T&x);
- uninitialized_fill_n ()能够使我们将内存配置与对象构造行为分离开来。它会为指定范围内的所有元素设定相同的初值。
- 如果[first,first+n)范围内的每一个迭代器都指向未初始化的内存,那么uninitialized_fill_n()会调用copy constructor, 在该范围内产生X(上式第三参数)的复制品。也就是说,面对[first,first+n)范围内的每个迭代器 i, uninitialized_fill_n()会调用construct(&*i, x), 在对应位置处产生x 的复制品。construct()前面讨论过。
- uninitialized_fill_n ()也具有"commitor rollback"语意:要么产生所有必要的元素,否则就不产生任何元素。如果任何一个copy constructor丢出异常(exception) , uninitialized_fill_n ()必须析构已产生的所有元素。
以下分别介绍这三个函数的实现法。其中所呈现的iterators(迭代器)、 value_type ()、_type_traits、_true_type、_false_type、is_POD_type 等实现技术,都将于第3章介绍。
(1).uninitialized_fill_n
首先是uninitialized_fill_n()的源代码。本函数接受三个参数:
-
迭代器frrst指向欲初始化空间的起始处。 -
n表示欲初始化空间的大小。 -
X表示初值。
template <class ForwardIterator,class Size, class T>
inline Forwarditerator uninitialized_fill_n (Forwarditerator first,Size n, const T& x) {
return uninitialized_fill_n(first,n, x, value_type(first));
}
这个函数的进行逻辑是,首先萃取出迭代器first的value type (详见第3 章),然后判断该型别是否为POD型别:
template<class Forwardlterator, class Size, class T, class T1>
inline ForwardIterator _uninitialized_fill_n (ForwardIterator first,Size n, const T& x, T1*)
{
typedef typename _type_traits<T1>::is_POD_type is_POD;
return __uninitialized_fill_n_aux(first,n,X, is_POD()) ;
POD意指Plain Old Data, 也就是标量型别(scalar types)或传统的C struct 型别。POD型别必然拥有trivial ctor / dtor /copy/ assignment函数,因此,我们可以对POD型别采用最有效率的初值填写手法,而对non-POD型别采取最保险安全的做法:
template <class Forwarditerator,class Size, class T>
inline Forwarditerator
__uninitialized_fill_n_aux(ForwardIterator first,Sizen,
const T& x, _true_type) {
returnfill_n(first,n, x);
}
template<class Forwarditerator, class Size, class T>
Forwarditerator
__uninitialized_fill_n_aux(Forwarditerator first, Size n,
const T& x, _false_type) (
Forwarditerator cur= first;
for (; n > 0; --n, ++cur)
construct (&*cur, x);
return cur;
}
(2) uninitialized_copy 下面列出uninitialized—copy()的源码。本函数接受三个参数:
-
迭代器first指向输入端的起始位置。 -
迭代器last指向输入端的结束位置(前闭后开区间)。 -
迭代器result指向输出端(欲初始化空间)的起始处。
template <class Inputiterator, class Forwarditerator> inline ForwardIterator
uninitialized_copy(Inputiterator first, Input.Iterator last,ForwardIterator result){
return _uninitialized_copy(first,last, result, value_type(result));
}
这个函数的进行逻辑是,首先萃取出迭代器result的valuetype (详见第3 章),然后判断该型别是否为POD型别:
template <class Inputiterator, class Forwarditerator, class T> inline ForwardIterator
__uninitialized—copy(Inputiterator first, Inputiterator last,Forwarditerator result, T*) {
typedef typename _type_trait<T>::is_POD_type is_POD;
return __uninitialized_copy_aux(first, last, result, is_POD());
}
POD意指Plain Old Data, 也就是标量型别(scalartypes)或传统的C struct型别。POD型别必然拥有trivial ctor / dtor /copy/ assignment函数,因此,我们可以 对POD观别采用最有效率的复制手法,而对non-POD型别采取最保险安全的做法:
template <class InputIterator,class Forwarditerator>
inline ForwardIterator
__uninitialized_copy_aux(InputIterator first, InputIterator last,
ForwardIterator result,
__true_type){
return copy(first, last, result); II调用STL算法copy()
}
template <class InputIterator,class Forwardlterator>
ForwardIterator
__uninitialized_copy_aux(InputIteratorfirst, InputIterator last,
ForwardIterator result,
_false_type) {
Forwarditerator cur= result;
for (; first != last; ++first,++cur)
construct (&*cur, *first);
return cur;
}
}
针对cha户和wchar_t*两种型别, 可以采用最具效率的做法mernmove (直接移动内存内容)来执行复制行为。因此SGI得以为这两种型别设计一份特化版本。
inline char* uninitialized_copy(const char* first, const char* last,har* result){
memmove(result, first, last - first);
return result + (last - first);
}
inline wchar_t* uninitialized_copy(const wchar_t* first, const wchar_ t* last,wchar_t* result) {
memmove(result, first, sizeof(wchar_t) * (last - first));
return result + (last - first);
}
(3) uninitialized_fill
下面列出uninitialized_fill ()的源代码。 本函数接受三个参数:
-
迭代器first指向输出端(欲初始化空间)的起始处。 -
迭代器last指向输出端(欲初始化空间)的结束处(前闭后开区间)。 -
X 表示初值。
template <class Forwarditerator, class T>
inline void uninitialized_fill (Forwarditerator first, Forwarditerator last,const T& x) {
__uninitialized_fill(first, last, x, value_type(first));
}
这个函数的进行逻辑是,首先萃取出迭代器first的valuetype (详见第3章), 然后判断该型别是否为POD型别:
template <class Forwarditerator, class T, class T1>
inline void uninitialized_fill(Forwarditerator first, Forwarditerator last,const T& x, T1*) {
typedef typename _type_traits<T1>::is_POD_type is_POD;
_uninitialized_fill_aux (first, last, x, is_POD ()) ;
}
POD意指Plain Old Data, 也就是标董型别(scalar types)或传统的C struct型别。POD型别必然拥有trivial ctor / dtor /copy/ assignment函数, 因此, 我们可以对POD型别采用最有效率的初值填写手法, 而对non-POD型别采用最保险安全的做法:
template <class Forwarditerator, class T>
inline void
__uniniialized_fill_aux(Forwarditeratorfirst, Forwarditerator last,const T&x, _true_type)
{
fill(first, last,x);
}
template <class Forwarditerator, class T>
void
__uninitialized_fill_aux(ForwardIterator first, Forwarditerator last,const T& x, _false_type)
{
Forwarditerator cur= first;
for(; cur != last; ++cur)
construct(&*cur, x) ;
}
}
以图形显示本节三个函数对效率的特殊考虑,三个内存基本函数的泛型版本与特化版本:
|