CXYVIP官网源码交易平台_网站源码_商城源码_小程序源码平台-丞旭猿论坛
CXYVIP官网源码交易平台_网站源码_商城源码_小程序源码平台-丞旭猿论坛
CXYVIP官网源码交易平台_网站源码_商城源码_小程序源码平台-丞旭猿论坛

第10篇:C++堆内存管理器-allocator-源码交易平台丞旭猿

首先什么是Allocator?Allocator有什么用?

分配器是负责封装堆内存管理的对象,它们在整个标准库中使用,特别是STL容器使用它们来管理r容器内部的所有内存分配,大部份情况下,程序员不用理会,标准容器使用默认的分配器称为std :: allocator,例如当你声明一个简单的vector对象时,C++编译器默认已经使用了内置的std::allocator,在标准库的vector模板当中,第二个模板参数_Alloc就是std::allocator,实际上,std::allocator也是一个类模板

intmain(){std::vector<int>a;}
先简要回顾一下历史。Alexander Stepanov(STL之父)提出了分配器的想法,其动机是使容器完全独立于底层内存模型。他打算让分配器完全封装内存模型,但标准委员会发现这很危险,因为这种方法会导致不可接受的效率下降。因此,分配器的当前用途是让程序员控制容器内的内存分配,而不是采用底层硬件的地址模型

本篇从零实现自己的内存分配器,来理解std::allocator的内部运行机制,自定义allocator有很多现实的原因。

  • 有些嵌入式平台没有提供默认的malloc/free等底层内存管理函数,你需要继承std::allocator,并封装自定义版本的malloc/free等更底层的堆内存管理函数。
  • 使用C++实现自己的数据结构,有时我们需要扩展(继承)std::allocator。
  • 大部分用C++写的游戏程序都有自己重新实现的allocator。

每个容器实例中都有一个Allocator实例。它向分配器请求存储来存储元素。分配器应具备的基本成员函数如下:

  • T*allocate(size_t n);分配足够的存储空间来存储T的n个实例,并返回指向它的指针
  • void deallocate(T* p, size_t n)释放分配的内存
  • void construct(T* p, Args … args);使用p指向的args参数构造一个对象,该接口在C++20中已被移除
  • void destroy(T* p);调用p指向的对象的析构函数,该接口在C++20中已被移除

标准库中的allocator还有一些可选的成员,视不同C++编译器版本而异。

  • 指向类型T的指针,以及指向类型为T的常量指针
  • 类型T的引用,以及类型T的常量引用
  • 自身类型T的别名value_type
  • 能表达allocation模型中最大对象大小的一个无符号整数类型,并且表示在allocation模型中两个指针区别(difference_type)的有符号整数
  • size_type max_size()返回有关类型T的分配内存的最大值。该接口在C++20中已被移除

MyAllocator实现

我们下面从0实现一个符合定义的allocator,首先我们要在调用层代码,在声明一个vector实例,显式传入我们自定义的MyAllocator,并使用他来分配内存

intmain(void){std::vector<int,MyAllocator<int>>v;}

下面是我们对MyAllocator的定义,并且定义了标准库规定的所有类型别名,下面的类型别名在标准库的std::allocator_traints中都提供了默认值,因此这里列出所有类型别名仅仅为了完整说明一个allocator的实现过程。

template<classT>classMyAllocator{public:usingvalue_typeT;usingpointer=T*;usingconst_pointer=constT*;usingvoid_pointer=void*;usingconst_void_pointer=constvoid*;usingsize_typesize_t;usingdifference_type=std::ptrdiff_tMyAllocator()=default;~MyAllocator()=default;};

allocator的堆内存管理接口

我们跟着下来需要实现的是allocate成员函数和dealloc成员函数,整个allocator的堆内存管理都围绕这两个接口展开。我们需要对C++的allocator的堆内存接口调用顺序有个清晰的认识,如下图所示。

allocator的堆内存管理接口

我们在MyAllocator实现的allocate和deallocate函数是必须的,

template<classT>classMyAllocator{public:.....pointerallocate(size_typenumObjs){returnstatic_cast<pointer>(operatornew(sizeof(T)*numObjs));}pointerallocate(size_typenumObjs,const_void_pointerhit){returnallocate(numObjs);}voiddeallocate(pointerp,size_typenumObjs){operatordelete(p);}}

我们这里也定义了另一个allocate版本的成员函数,接受一个numObjs参数,并接受一个已分配堆内存的指针,他是一个只想最近分配的元素的指针。可以使用他来改进已分配内存的释放,只是为了提供缓存性能,在我们的示例中,我们会忽略它并立即返回。

其他的成员函数实现

size_typemax_size()const{returnstd::numeric_limits<size_type>::max();}

对象构造的问题这是C++20中,被丢弃的内容,但为了完整说明,我在本篇中仍然会提及该内容。

C++20之前的标准库,在allocator中,我们有两个的方法用于构造和销毁对象,一种叫construct,用于在我们分配的内存中构造类型T的实际对象。需要注意的是,当我们调用new操作符分配了原始内存,new操作符并不会执行任何类型T的初始化.

我们看看构造函数是如何工作的,首先,对于allocator下的construct方法,它也是一个类模板的成员函数,如下代码定义,该成员函数接受一个类型U的指针p(已分配了堆内存),construct的运行原理非常简单,他在construct函数内部原地调用类型U的构造函数,并且转发外部的任意参数传递给类型U的构造函数

template<classT>classMyAllocator{public://类型别名的代码省略//其余代码略//构造函数template<classU,class...Args>voidconstruct(U*p,Args&&...){new(p)U(std::forward<Args>(args)...);}}

同理的destroy接口实现如下

template<classT>classMyAllocator{public://类型别名的代码省略//其余代码略//构造函数template<classU,class...Args>voiddestroy(U*p){p->~U();}}

这两个函数默认情况下是完全可选,我这里只是展示如何完整的MyAllocator的自定义实现。

内置绑定机制

上面实现的必选项和可选项的allocator的类成员,都是标准库已经规定的接口名称,没什么好说的。而allocator的难点就是rebind这个内部类成员,而且它也是一个类模板。

rebind内部类成员根据标准库的定义,rebind被定义为std::allocator类的结构成员; 此结构定义了other成员,该成员定义为专门用于不同参数类型的分配器的实例(other成员定义了可以创建不同类型的对象的分配器类)

classMyAllocator{public:.....template<classU>structrebind{usingother=MyAllocator<U>;}}

std::MyAllocator模板用于获取某种类型T的对象。 容器内部可能需要分配不同类型的对象。 例如,当您有一个std :: list 时,分配器MyAllocator用于分配类型T的对象,而std :: list 实际上需要分配某个节点类型Node的对象,从模板函数的角度来考虑,我们通常会用另为一个模板参数U来表示 调用节点类型Node,std :: list 需要获得类型U对象的分配器,该分配器使用MyAllocator提供的分配机制。对于程序员在定义自己的数据结构时,需要在自己实现的容器内部定义类似如下的语句

typenameMyAllocator::templaterebind<U>::other

指定相应的类型。 现在,此声明中有一些语法上令人困惑的地方:

  1. 由于rebind是MyAllocator的成员模板,而MyAllocator是std::list模板参数,因此rebind成为从属名称。 为了表明从属名称是模板,需要在其前面加上template前缀。 如果没有template关键字,则<将被视为小于运算符。
  2. other也取决于模板参数,即它也是一个从属名称。 为了表明从属名称是一种类型,需要typename关键字。

那么,我们在自定义的数据结构当中可能多次用到类U类型的allocator实例,那么我们可以在自定义的数据结构容器类的private作用域定义一个类型别名

typedeftypenameMyAllocator::templaterebind<U>::otherElem_alloc_type

关于,MyAllocator说到这里应该是比较全面的了,他的全貌大概如下代码,若要确认该自定义的MyAllocator和std::allocator一样能够对所有std的容器起到作用,不妨在增加一些计数器的数据成员

includeincludeincludetemplate<classT>classMyAllocator{public://下面是个类型别名,实现中的可选项usingvalue_type=T;usingpointer=T*;usingconst_pointer=constT*;usingvoid_pointer=void*;usingconst_void_pointer=constvoid*;usingsize_type=size_t;usingdifference=std::ptrdiff_t;//重新绑定函数template<classU>structrebind{usingother=MyAllocator<U>;};MyAllocator()=default;~MyAllocator()=default;//分配内存pointerallocate(size_typenumObjects){allocCount+=numObjects;std::cout<<"MyAllocator::allocate,内存分配:"<<numObjects<<std::endl;returnstatic_cast<pointer>(operatornew(sizeof(T)*numObjects));}//分配内存pointerallocate(size_typenumObjects,const_void_pointerhint){returnallocate(numObjects);}//释放内存voiddeallocate(pointerp,size_typenumObjects){std::cout<<"MyAllocator::deallocate,内存释放:"<<numObjects<<std::endl;allocCount=allocCount-numObjects;operatordelete(p);}//分配器支持最大的内存数size_typemax_size()const{returnstd::numeric_limits<size_type>::max();}// //构造对象// template // void construct(U *p, Args &&... args)// {//     new (p) U(std::forward(args)...);// }// //销毁对象// template // void destroy(U *p)// {//     p->~U();// }//返回每次分配/删除的内存数size_typeget_allocations()const{returnallocCount;}private://统计当前内存的使用量size_typeallocCount;

我们的调用代码基本如下

include"headers/Allocator.hh"includeincludeincludeincludeincludeintmain(){std::vector<int,MyAllocator<int>>v(0);for(size_ti=0;i<30;i++){sleep(1);v.push_back(i);std::cout<<"当前容器内存占用量:"<<v.get_allocator().get_allocations()<<std::endl;}}

用vector执行下面的测试如下

测试效果

小结

这里没有生硬地罗列一堆std::allocator的api,而是采用原生实现的MyAllocator来说明std::allocator的内部机制。

  • 分配器是STL容器的内存分配器。该容器可以将内存分配和取消分配与其元素的初始化和销毁分开。 因此,调用向量vec的vec.reserve(n)仅为至少n个元素分配内存。 每个元素的构造函数都不会执行。因为我们在最终的MyAllocator的construct接口和destoy接口取消了。
  • 我们可以根据需要的容器来调整分配器,例如,您只希望vector内存resize操作的比例
  • new不允许控制调用哪些构造函数,而只能同时构造所有对象。 这是std ::分配器优于new的优势

读后有收获可以微信或支付宝请笔者喝杯咖啡

声明:本文部分素材转载自互联网,如有侵权立即删除 。

© 版权声明
THE END
喜欢就支持一下吧
点赞0赞赏 分享
相关推荐
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容