SGISTL源码探究-空间配置器

前言

该系列的博客主要是看SGISTL3.0源码,并参考了一些相关的博客还有《STL源码剖析》一书得到的心得及一些自己的理解整理而成,如有错误请指出,谢谢。

allocator

引入

事实上,我们在学习C++的时候,接触到空间配置器应该就是allocator,它用于申请一块未初始化的空间,这是因为new会执行两个动作,首先申请空间,其次调用其构造函数。但是如果有些空间根本就不会用到,那么调用构造函数初始化会浪费效率。而allocator解决了这个问题,即分配/释放内存时只调用mallocfree,而将构造/析构动作分开。(其实newfree一般也是调用这两个函数,但是又额外增加了初始化以及析构动作)

代码实例
#include <iostream>
using namespace std;
#include <memory>
#include <vector>

int main()
{
    //定义一个配置器对象
    allocator<vector<int> > alloc;
    //使用该配置器分配3个未初始化的vector<int>
    vector<int>* p = alloc.allocate(3);
    vector<int>* q = p;
    for(int i = 0; i < 3; i++)
    {
        //调用vector<int>的构造函数
        alloc.construct(q++, 1, i*10);
        //输出(此时相当于是二维数组p[3][])
        cout << p[i][0] << endl;
    }
    //循环调用析构函数
    while(q != p)
    {
        alloc.destroy(--q);
    }
    //释放分配的空间
    alloc.deallocate(p, 3);
    return 0;
}

编译链接g++ -std=c++11 allocator.cpp
以上便是allocator空间配置器最简单的使用。从中我们可以看出要使用allocator配置器,需要包含头文件<memory>。以及有如下几个基本的接口:

  1. allocate(size_t n):用于分配n个对象
  2. construct(T* p, args):用于构造类型为T*的指针p指向的对象,args是构造函数的参数
  3. destroy(T* p):即对类型为T*的指针p指向的对象调用对应的析构函数
  4. deallocate(T* p, size_t n):用于释放内存,需要注意指针p指向的一定要是分配时的起始地址,否则可能造成未定义的结果。
深入源码

下面我们就来看看allocator的那些接口是如何实现的,首先我们还是先从construct()destroy()入手,因为allocate()稍微复杂一些,涉及了两级配置器,第一级用于申请超过128bytes的空间,第二级用于申请小空间,采用了内存池技术。

  • construct()
template <class T1, class T2>
inline void construct(T1* p, const T2& value)
{
  new (p) T1(value);
}

代码其实就一行new (p) T1(value),但是new不是会分配新的内存吗,你可能会有这样的疑惑。其实这里的new跟你认识的operator new不太一样,而是placement new,它重载了operator new,用于在一个已经分配好的内存中(堆/栈)构造一个新的对象。这也是一种显示调用构造/析构函数的一种方法,另一种直接在函数前加作用域。那么这样我相信你就可以理解了。(ps:使用placement new需要包含头文件<new.h>)

  • destroy
    destroy其实有多个版本,针对了不同的情况。

    1. 传入一个指针
    2. 传入两个迭代器,有trivial destructor
    3. 传入两个迭代器,有non-trivial destructor
    4. 迭代器是char *和wchar *

    情况一:

    template<class T>
    inline void destroy(T* pointer)
    {
    pointer->~T();
    }

    这种情况没什么好说的
    情况二和情况三的共同部分:
    在具体到情况二和三之前,有一些公共的内部接口

    template <class ForwardIterator>
    inline void destroy(ForwardIterator first, ForwardIterator last)
    {
    __destroy(first, last, value_type(first));
    }
    
    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());
    }

    首先是接受两个迭代器作为范围的destroy,它内部调用__destroy,value_type()用于获取迭代器所指对象的类型,运用traits技法实现,至于具体如何实,在分析迭代器时会讲解。而在__destroy中,内部调用__destroy_aux,最后一个参数是trivial_destructor(),其实这是一个typedef,跟__type_traits有关,也放在后面讲解,现在你只用知道它的作用是判断是否有trivial destructor就行了。

    情况二:

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

    这个情况是针对non-trivial destructor,意思也就是在[first,last)范围内,调用情况一的函数,即依次调用析构函数。看第三个参数__false_type,很明显,它是一个对象,嗯….从中你可能会看出点什么。
    情况三:

    template <class ForwardIterator>
    inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type){}

    这里唯一可以让我们关注的就只有__true_type了,跟情况二结合不难发现,通过trivial_destructor()的返回值是__true_type还是__false_type,而选择不同的情况。而trivial_destructor其实是个typedef定义的,根据不同的情况,分别为__true_type__false_type,比如基础类型int之类的,肯定就typedef成__true_type了。__true_type__false_type是两个空类。trivial_destructor()即构造一个对象。
    情况四:

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

    如你所见,这两种类型根本不需要做什么。

分了这几种情况,主要是在效率方面上的考虑,有一些对象本来的析构函数就属于没什么用的,调用这种析构函数只会浪费时间,特别是在范围析构的时候。

小结

在本小节中,我们主要了解了allocator的简单的用法以及constructdestroy函数针对不同的情况采取的最具效率的做法。前面已经提到过分为两级配置器,所以allocatedeallocate对应的也应该有两种,一种是第一级配置器,另一种是第二级配置器。在下节中,我们将看看第一级配置器是如何分配以及释放内存的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值