C++ allocator类学习理解

前言

在学习STL中containers会发现C++ STL里定义了很多的容器(containers),每一个容器的第二个模板参数都是allocator类型,而且默认参数都是allocator。但是allocator到底是什么?有什么作用呢?

接下来就围绕着是什么和有什么作用来展开,其中最后补充一下如何去使用默认的allocator。

由于本人学习尚浅,各种blog和msdn学习了几天,依然还是不是特别理解。这是把自己的学习经验,进行一次梳理和记录。

 

What?

 

std::allocator

template <class T> class allocator;    // 默认分配器

 

默认分配器

所有的分配器都定义在 <memory> 头文件中,被用于标准库中的STL containers

如果标准容器中最后一个模板参数没有指定,那么就是allocator默认参数

 对分配或释放存储的成员函数的调用将发生在一个总的顺序中,并且每个这样的释放将在下一个分配(如果有的话)之前发生。
 

 主要成员函数

  • address
     
     函数原型:
     pointer address (reference x) const noexcept;
     
     const_pointer address ( const_referece x );
     
     //    reference => T&          const_reference => const T&

  返回x的地址

 

  • allocate
     
     函数原型:
     pointer allocate(size_type n, allocator<void>::const_pointer hint = 0);
     //hint:0 or 通过另外一个allocate获得的非零值且没有使用deallocate释放
     //当 hint != 0 时,这个值可以作为一个提示,通过分配接近指定的新存储块来提高性能。相邻元素地址是一个不错的选择
     pointer => T* const_pointer => const T* size_type => size_t
     
     分配存储块
     尝试分配n个T类型的存储空间,然后返回第一个元素的起始地址
     只是分配空间,不构造对象
     
     在标准默认allocator,存储块是使用 一次或多次 ::operator new 进行分配,如果他不能分配请求的存储空间,则抛出bad_alloc异常

 

  • construct

     原型函数:
     template <class U, class... Args>
     void construct(U* p, Args&&... args);

     在p指向的位置构建对象U,此时该函数不分配空间,pointer p是allocate分配后的起始地址
     constructor将其参数转发给相应的构造函数构造U类型的对象,相当于 ::new ((void*) p) U(forward<Args> (args)...);

 

  • deallocate
     
     原型函数:
     void deallocate(pointer p, size_t n);
     
     释放先前allocate分配的且没有被释放的存储空间
     
     p:指向以前使用allocator :: allocate分配的存储块的指针。
     n:在调用allocator :: allocate时为这个存储块分配的元素数量。
     

     在默认的allocator中,使用 ::operator delete进行释放

 

  • destroy
     
     原型函数:
     template <class U>void destroy (U* p);
     
     销毁p指向的对象,但是不会释放空间,也就意味着,这段空间依然可以使用
     该函数使用U的析构函数,就像使用下面的代码一样:P->〜U();

 

  • max_size

     原型函数:
     size_type max_size() const noexcept;

     返回最大可能分配的大小

How?

有关allocator的最重要的事实是它们只是为了一个目的:封装STL容器在内存管理上的低层细节。你不应该在自己的代码中直接调用 allocator 的成员函数,除非正在写一个自己的STL容器。你不应该试图使用allocator来实现operator new[];这不是allocator该做的。 如果你不确定是否需要使用allocator,那就不要用。

 

基本上很少有人会自定义一个allocator。一来,默认的allocator已经够用了;二来,确实不知道该怎么用。一般来说,我们没有必要重新定义一个allocator。自定义的方式主要是为了提高内存分配相关操作的性能。而STL提供的方式性能已经足够好了。

 

使用默认allocator

使用步骤:

由于allocator将内存空间的分配和对象的构建分离,故使用allocator分为以下几步:

  1. allocator与类绑定,因为allocator是一个泛型类
  2. allocate()申请指定大小空间
  3. construct()构建对象,其参数为可变参数,所以可以选择匹配的构造函数
  4. 使用,与其它指针使用无异
  5. destroy()析构对象,此时空间还是可以使用
  6. deallocate()回收空间

请认真遵守这个顺序使用,不然会无法预料的异常

( 下面该程序也可以解决无默认参数来构造对象数组的问题)

//#include "CAnimal.h"
#include <memory>
#include <iostream>

using namespace std;

class Animal
{
public:
#if 1        //即使为0,没有默认构造也是可以,
    Animal() : num(0)
    {
        cout << "Animal constructor default" << endl;
    }
#endif
    Animal(int _num) : num(_num)
    {
        cout << "Animal constructor param" << endl;
    }

    ~Animal()
    {
        cout << "Animal destructor" << endl;
    }

    void show()
    {
        cout << this->num << endl;
    }

private:
    int num;
};

int main()
{
    allocator<Animal> alloc;        //1.
    Animal *a = alloc.allocate(5);    //2.

    //3.
    alloc.construct(a, 1);
    alloc.construct(a + 1);
    alloc.construct(a + 2, 3);
    alloc.construct(a + 3);
    alloc.construct(a + 4, 5);

    //4.
    a->show();
    (a + 1)->show();
    (a + 2)->show();
    (a + 3)->show();
    (a + 4)->show();

    //5.
    for (int i = 0; i < 5; i++)
    {
        alloc.destroy(a + i);
    }
    //对象销毁之后还可以继续构建,因为构建和内存的分配是分离的
    //6.
    alloc.deallocate(a, 5);

    cin.get();
    return 0;
}

通过运行结果可以看出,无论是否有默认构造,allocator会选择出最匹配的构造函数(重载) 

 

结语:由于现在自己木有工作经验和项目经验,实在对这个allocator的使用,懵懵懂懂,在使用STL containers时,也没有看见自定义的Allocator,现在只能简单学习了解,以便以后工作捡起来不那么难或者在看大神的代码的时候不在那么懵逼。。。

好吧!就这样吧。。。

 

 

posted @ 2017-11-17 20:32  SimonKly  阅读(21274)  评论(1编辑  收藏  举报