zoukankan      html  css  js  c++  java
  • 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().

     

  • 相关阅读:
    SCOI2003 字符串折叠
    UVA1629 Cake slicing
    POI2008 KLO-Building blocks
    NOI导刊2010提高 符文之语
    MongoDB数据库的基本操作
    React Naive 解决防止多次点击的解决方法
    如何自定义修改博客园样式
    语法对照表ES5VSES6
    MongoDB数据库安装
    小程序学习2 常用小程序概念以及代码实现
  • 原文地址:https://www.cnblogs.com/wuchanming/p/4060912.html
Copyright © 2011-2022 走看看