STL——空间配置器(构造和析构基本工具)

以STL的运用角度而言,空间配置器是最不需要介绍的东西,它总是隐藏在一切组件(更具体地说是指容器,container)的背后,默默工作,默默付出。但若以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象(所有的数据)都存放在容器之内,而容器一定需要配置空间以置放资料。

为什么不说allocator是内存配置器而说它是空间配置器呢?因为空间不一定是内存,空间也可以是磁盘或其它辅助存储介质。是的,你可以一个allocator,直接向硬盘取空间,以下介绍的是SGI STL提供的配置器,配置的对象是内存。

空间配置器的标准接口

根据STL的规范,以下是allocator的必要接口:

allocator::value_type
allocator::pointer
allocator::const_pointer
allocator::reference
allocator::const_reference
allocator::size_type
allocator::difference_type

allocator::rebind
一个嵌套的class template,class rebind<U>拥有唯一的成员other,那是一个typedef,代表allocator<U>

allocator::allocator()
default constuctor

allocator::allocator(const allocator&)
copy constructor

template<class U>allocator::allocator(const allocator<U>&)
泛化的copy constructor

allocator::~allocator()
destructor

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,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& x)
等同于 new((void*)p) T(x)     placement new


void allocator::destroy(pointer p)
等同于p->~T()

SGI 空间配置器

SGI STL的配置器与众不同,它与标准规范不同。如果要在程序中明白采用SGI配置器,那么应该这样写:

vector<int, std::alloc> iv; //gcc编译器

配置器名字为alloc,不接受任何参数。标准配置器的名字是allocator,而且可以接受参数。比如VC中写法:

vector<int, std::allocator<int> > iv; //VC编译器

SGI STL的每一个容器都已经指定了缺省配置其alloc。我们很少需要自己去指定空间配置器。比如vector容器的声明:

template <class T, class Alloc = alloc>
class vector {
//...
}

SGI标准的空间配置器allocator

其实SGI也定义了一个符合部分标准,名为allocator的配置器,但是它自己不使用,也不建议我们使用,主要原因是效率不佳。它只是把C++的操作符::operator new和::operator delete做了一层简单的封装而已。下面仅仅贴出代码:

#ifndef DEFALLOC_H
#define DEFALLOC_H

#include <new.h>
#include <stddef.h>
#include <stdlib.h>
#include <limits.h>
#include <iostream.h>
#include <algobase.h>

//仅仅是简单的封装了operator new
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;
}

//仅仅是简单的封装了operator::delete
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(1), size_type(4096/sizeof(T))); 
    }
    size_type max_size() const { 
    return max(size_type(1), size_type(UINT_MAX/sizeof(T))); 
    }
};

class allocator<void> {
public:
    typedef void* pointer;
};

#endif

SGI特殊的空间配置器alloc

一般而言,我们所习惯的C++内存配置器操作和释放操作时这样的:

class FOO { ...};
FOO* pf=new FOO;  //配置内存,然后构造对象
delete pf;  //将对象析构,然后释放内存

这其中的

new算式内含两个阶段操作:

(1)调用::operator new 配置内存

(2)调用FOO::FOO()构造对象内容

delete算式也内含两个阶段操作:

(1)调用FOO::~FOO()对对象析构

(2)调用::operator delete释放内存

为了精密分工,SGI allocator将两个阶段分开

内存配置操作由alloc:allocate负责,内存释放由alloc:deallocate负责;对象构造操作由::contructor()负责,对象析构由::destroy()负责。

STL标准告诉我们,配置器定义在头文件<memory>中,它里面又包括两个文件:

#include <stl_alloc.h>		// 负责内存空间的配置和器释放
#include <stl_construct.h>		// 负责对象的构造和析构

内存空间的配置/释放与对象内容的构造/析构,分别落在这两个文件身上。其中<stl_construct.h>定义了两个基本函数:构造用的construct()和析构用的destroy().

下图显示了其结构:

 

 

图一 头文件 <memory>结构

构造函数析构的基本工具:construct()和destroy()

下面是<stl_constuct.h>的部分内容:

函数construct()使用了定位new操作符,其源代码:

template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
  new (p) T1(value);     // 定为new操作符placement new; 在指针p所指处构造对象
}

函数destroy则有两个版本。

第一个版本较简单,接受一个指针作为参数,直接调用对象的析构函数即可,其源代码:

template <class T>
inline void destroy(T* pointer) {
    pointer->~T();    // 调用析构函数
}

第二个版本,其参数接受两个迭代器,将两个迭代器所指范围内的所有对象析构掉。而且,它采用了一种特别的技术:依据元素的型别,判断其是否有trivial destructor(无用的析构函数)进行不同的处理。这也是为了效率考虑。因为如果每个对象的析构函数都是trivial的,那么调用这些毫无作用的析构函数会对效率造成影响。

下面看其源代码:

// 以下是 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)有 trivial destructor…
template <class ForwardIterator> 
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}//不调用析构函数

// 如果元素的数值型別(value type)有 non-trivial destructor…
template <class ForwardIterator>
inline void
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
  for ( ; first < last; ++first)
    destroy(&*first);//调用析构函数
}

第二版本还针对迭代器为char*和wchar_t*定义了特化版本:

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

图二显示了这两个函数的结构和功能。他们被包含在头文件stl_construct.h中。

图二 函数construct()和destroy()示意图

这两个座位构造、析构之用的函数被设计为全局函数,符号STL的规范。此外,STL还规定配置器必须拥有名为construct()和destroy()的两个成员函数。

上述construct()接收一个指针p和一个初值value,该函数的用途就是将初值设定到指针所指的空间上。C++的placement new运算可用来完成这一任务。

destroy()有两个版本,第一版本接受一个指针,准备将该指针所指之物析构掉。这很简单,直接调用该对象的析构函数即可。第二版本接受first和last迭代器,准备将[first,last)范围内的所以对象析构掉。我们不知道这个范围有多大,万一很大,而每个对象的析构函数都无关痛痒(所谓的trivial destructor),那么一次次调用这些无关痛痒的析构函数,对效率是一种伤害。因此,这里先利用value_type()获得迭代器所指对象的型别,再利用_type_traits<T>判断该型别的析构函数是否无关痛痒。若是(_true_type),则什么也不做就结束;若否,(_false_type),这才以循环方式巡访整个范围,并在循环中每经历一个对象就调用一个版本的destroy().

 

原文地址:https://www.cnblogs.com/wuchanming/p/4060912.html