侯捷STL课程及源码剖析学习2: allocator

时间:2023-03-10 02:23:36
侯捷STL课程及源码剖析学习2: allocator

  以STL 的运用角度而言,空间配置器是最不需要介绍的东西,它总是隐藏在一切组件(更具体地说是指容器,container)的背后,默默工作默默付出。

一、分配器测试

测试代码

 #include <list>
#include <stdexcept>
#include <string>
#include <cstdlib> //abort()
#include <cstdio> //snprintf()
#include <algorithm> //find()
#include <iostream>
#include <ctime> #include <cstddef>
#include <memory> //内含 std::allocator
//欲使用 std::allocator 以外的 allocator, 得自行 #include <ext\...>
#ifdef __GNUC__
#include <ext\array_allocator.h>
#include <ext\mt_allocator.h>
#include <ext\debug_allocator.h>
#include <ext\pool_allocator.h>
#include <ext\bitmap_allocator.h>
#include <ext\malloc_allocator.h>
#include <ext\new_allocator.h>
#endif namespace jj20
{
//pass A object to function template impl(),
//而 A 本身是个 class template, 带有 type parameter T,
//那么有无可能在 impl() 中抓出 T, 创建一个 list<T, A<T>> object?
//以下先暂时回避上述疑问. void test_list_with_special_allocator()
{
#ifdef __GNUC__
cout << "\ntest_list_with_special_allocator().......... \n"; //不能在 switch case 中宣告,只好下面这样. //1000000次
list<string, allocator<string>> c1; //
list<string, __gnu_cxx::malloc_allocator<string>> c2; //
list<string, __gnu_cxx::new_allocator<string>> c3; //
list<string, __gnu_cxx::__pool_alloc<string>> c4; //
list<string, __gnu_cxx::__mt_alloc<string>> c5; //
list<string, __gnu_cxx::bitmap_allocator<string>> c6; //4781 int choice;
long value; cout << "select: "
<< " (1) std::allocator "
<< " (2) malloc_allocator "
<< " (3) new_allocator "
<< " (4) __pool_alloc "
<< " (5) __mt_alloc "
<< " (6) bitmap_allocator "; cin >> choice;
if (choice != ) {
cout << "how many elements: ";
cin >> value;
} char buf[];
clock_t timeStart = clock();
for (long i = ; i< value; ++i)
{
try {
snprintf(buf, , "%d", i);
switch (choice)
{
case : c1.push_back(string(buf));
break;
case : c2.push_back(string(buf));
break;
case : c3.push_back(string(buf));
break;
case : c4.push_back(string(buf));
break;
case : c5.push_back(string(buf));
break;
case : c6.push_back(string(buf));
break;
default:
break;
}
}
catch (exception& p) {
cout << "i=" << i << " " << p.what() << endl;
abort();
}
}
cout << "a lot of push_back(), milli-seconds : " << (clock() - timeStart) << endl; //test all allocators' allocate() & deallocate();
int* p;
allocator<int> alloc1;
p = alloc1.allocate();
alloc1.deallocate(p, ); __gnu_cxx::malloc_allocator<int> alloc2;
p = alloc2.allocate();
alloc2.deallocate(p, ); __gnu_cxx::new_allocator<int> alloc3;
p = alloc3.allocate();
alloc3.deallocate(p, ); __gnu_cxx::__pool_alloc<int> alloc4;
p = alloc4.allocate();
alloc4.deallocate(p, ); //我刻意令参数为 2, 但这有何意义!! 一次要 2 个 ints? __gnu_cxx::__mt_alloc<int> alloc5;
p = alloc5.allocate();
alloc5.deallocate(p, ); __gnu_cxx::bitmap_allocator<int> alloc6;
p = alloc6.allocate();
alloc6.deallocate(p, ); //我刻意令参数为 3, 但这有何意义!! 一次要 3 个 ints?
#endif
}
}

注:直接使用分配器allocate时,释放时须指明释放内存大小。

二、简单的内存分配器

一般而言,c++的内存分配和释放是这样操作的

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

  其中的 new操作内含两阶段动作:(1)调用::operator new配置内存,(2) 调用Foo::Foo()建构对象内容。delete操作也内含两阶段动作: (1)调用Foo::~Foo()将对象解构,(2)调用::operator delete释放内存。

  为了精密分工,STL allocator决定将这两阶段区分开来。内存配置由alloc:allocate()负责,内存释放由alloc::deallocate()负责; 对象建构由::construct()负责,对象析构由::destroy()负责。

简单内存分配器代码

 #ifndef SIMPLE_JJALLOC_H
#define SIMPLE_JJALLOC_H
#include<new> //for placement new
#include<cstddef> //for ptrdiff_t,size_t
#include<cstdlib> //for exit()
#include<climits> //for UINT_MAX十进制的最大值
#include<iostream> //for cerr namespace JJ
{
/*****************ptrdiff_t与size_t类型*****size_type与difference_type类型********************
****ptrdiff_t:signed类型,通常用于两指针减法操作的结果,它可以是负数。(因为指针相减有正有负)
****size_t:unsigned类型,用于指明数组长度,它是非负整数。
****size_type:unsigned类型,容器中元素长度或下表,vector<int>::size_type i=0;
****difference_type:signed类型,表示迭代器差距,vector<int>::difference_type=iter1-iter2
****前两者位于标准类库std内,后两者为stl对象所有
*********************************************************************************************/
template<class T>
inline T* _allocate(ptrdiff_t size, T*)
{
std::cout<<"I'm _allocate in simple_jjalloc!"<<std::endl;
/**************************new_handler与set_new_handler***********************************
****new_handler:内存分配失败后,调用的处理函数。
****set_new_handler:参数是被指定的new_handler函数指针,返回参数也是new_handler是被替换掉的new_handler
*****************************************************************************************/
std::set_new_handler();
/****************::***********************************************************************
****"::":全局作用。比如::luo这就是个全局变量,而luo这是个局部变量
****"::":类作用。比如Node::function()
****"::":名字空间。比如std::size_t
*****************************************************************************************/
T *tmp=(T*)(::operator new((size_t)(size*sizeof(T))));
if(tmp == )//没有前面的std::set_new_handler(0);把内存分配失败后的异常调用函数给替换掉,就执行不到这儿
{
std::cout<<"failed!"<<std::endl;
std::cerr<<"out of memory"<<std::endl;
exit();
}
return tmp;
} template<class T>
inline void _deallocate(T* buffer)
{
::operator delete(buffer);
}
/************************************new的三种形态*******************************************
****new operator:就是平常用的new,通常做三件事,1.用operator new分配内存给对象,2.调用构造函数初始化那块内存,3.将地址转给对象指针
如果仅仅是在堆上建立对象,那么应该使用new operator,它会提供周全的服务
****operator new:在默认情况下首先会调用分配内存的代码,尝试从堆上得到一段空间,成功就返回,失败就调用new_hander,重复前面过程,直到抛出异常
如果仅仅是分配内存,那么应该调用operator new,但初始化不在它的职责之内。若对默认的内存分配过程不满意,那就重载它
****placement new:用来实现定位构造,可以通过它来选择合适的构造函数。
如果想在一块已获得的内存里建立一个对象,那就改用placement new
********************************************************************************************/
template<class T1,class T2>
inline void _construct(T1* p,const T2& val)
{
new(p) T1(val);//p为那块内存地址,T1()为指定构造函数;此句为p->T1::T1(val);
std::cout<<"I'm _construct!"<<std::endl;
} template<class T>
inline void _destroy(T* ptr)
{
std::cout<<"I'm _destroy!"<<std::endl;
ptr->~T();
} template<class T>
class mallocator
{
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; template<class U>
struct rebind//干吗用?见下
{
typedef mallocator<U> mother;
}; pointer allocate(size_type n,const void* hint=)
{
return _allocate((difference_type)n,(pointer));
} void deallocate(pointer p,size_type n)
{
_deallocate(p);
} void construct(pointer p,const_reference val)
{
_construct(p,val);
} void destroy(pointer p)
{
_destroy(p);
} pointer address(reference x)
{
return (pointer)&x;
} const pointer const_address(const_reference x)
{
return (const pointer)&x;
} size_type max_size()const
{
return size_type(UINT_MAX/sizeof(value_type));
}
};
}
#endif

内存分配测试代码:

int ia[5] = {1,2,3,4,5};

vector<int,JJ::mallocator<int>> iv(ia,ia+3);

for (auto i : iv)
cout<<i<<" ";
cout<<endl;

测试结果

I'm _allocate in simple_jjalloc!
I'm _construct!
I'm _construct!
I'm _construct!
1 2 3
I'm _destroy!
I'm _destroy!
I'm _destroy!

直接使用allocate, 不用new. 代码环境GNU 2.9 ,高版本头文件找不到。

 #include<iostream>
#include<defalloc.h>
#include<stl_alloc.h>
using namespace std;
void main()
{
void* p = allocator<int>().allocate();
allocator<int>().deallocate((int*)p);
cout << "allocator<int>().allocate(512);"<<endl; void* p2 = alloc::allocate();
alloc::deallocate(p2, ); cout << "allocate(512, 0);" << endl;
}

二、 OOP(Object-Oriented Programming) vs GP(Generic Programming)

  OOP是将数据和方法封装在一起,多采用继承和多态。

  GP却是将datas 和 methods 分开来。如STL中,Container 和 Algorithms 可以各自独立进行开发,两者使用迭代器进行关联。Algorithms通过iterators 确定操作范围,并获取容器元素。

深入理解留待后续。。。

内容参考:

侯捷STL课程, 《STL源码剖析》