内存分配器 Allocator 是为容器元素配置内存空间的类,从使用者的角度来说,Allocator一般隐藏在幕后,且无特殊需要,不会关注它。
然而,Allocator是容器创建的基石,是自己实现STL容器必不可少的第一步,同时,Allocator的设计会影响容器使用的效率。
在这里,为了尽快进入STL更加激动人心的实现部分,先仅仅实现了一个最简版本的 default allocator,后续可以在该版本之上再针对Allocator进行优化和扩充。
STL定义的Allocator的接口
STL对Allocator进行了规定,给出了Allocator的allocator 标准接口,可以参见cplusplus网站的说明。在这里,列出如下:
1.模板定义
template
class allocator;
2. 类型定义
member | definition | comment |
---|---|---|
value_type | T | 元素类型 |
pointer | T* | 元素指针 |
reference | T& | 引用 |
const_pointer | const T* | 常量指针 |
const_reference | const T& | 常量引用 |
size_type | size_t | 元素数量 |
difference_type | ptrdiff_t | 两个指针之间的距离 |
rebind | member class | Its member type other is the equivalent allocator type to allocate elements of type Type |
3. 成员函数
member function | comment |
---|---|
(constructor) | 可选的构造函数 |
(destructor) | 可选的析构函数 |
allocate | 分配内存 |
deallocate | 释放内存 |
construct | new相应的对象 |
destroy | delete相应的对象 |
address | 返回对象地址,还有const版本 |
max_size | 返回可成功配置的最大量 |
allocator模板类的实现
1.类型定义
STL定义的接口中,difference_type、reference等名称看起来有些怪怪的,针对一个分配器为何要定义这么复杂的类型?事实上,这些内容和后续的迭代器设计及traits编程技法是相关的,这里定义这些是为了和迭代器等类保持一致。这些变量的定义直接用typedef
关键字即可。
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;
//rebind allocator of type U.
template <class U>
struct rebind
{ typedef allocator<U> other; };
//... funcs
}
2. 成员函数定义
在规定的成员函数中,最重要的四个接口是allocate()
、deallocate()
、construct()
和destory()
,其中前两个负责空间的分配与释放,后两个负责对象的构造与析构。为何要将空间操作和对象操作分开呢?
事实上,我们比较习惯的C++内存分配方式是基于new
和delete
,如下方式,new
和delete
都包含空间和对象的操作。
class A
{ .... /*data and funcs*/ };
A* obj = new A(); //new中包含 空间配置 和 对象构造
delete A; //delete中包含对象析构和空间释放
但是对于泛化的情况来说,空间操作和对象操作一起操作在某些情况下可能效率很低,因此出于效率的考虑,将这两部分独立开来设计。
虽然有这样的考虑,但是在这里为了设计最简单的分配器,在具体实现上并没有考虑太多,仅仅对new
和delete
进行了简单的包装,可能效率还更低一点,但是在这样的架构下,优化的潜力是不可估量的。
为了程序的复用性,在这里,首先给出了函数模板形式的_allocate()
、_deallocate()
、_construct()
和_destory()
:
其中,_construct()
利用C++的 placement new
运算子将初值设定到指针所指的空间上。
template <class T>
inline T* _allocate(ptrdiff_t size, T*)
{
std::set_new_handler(0);
T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
if (tmp == 0)
{
std::cerr << "out of memeory" << std::endl;
exit(1);
}
return tmp;
}
template <class T>
inline void _deallocate(T* buffer)
{ ::operator delete(buffer);}
template <class T1, class T2>
inline void _construct(T1* p, const T2& value)
{ new(p) T1(value); } // placement new.invoke constructor of T1
template <class T>
inline void _destory(T* ptr)
{ ptr->~T(); }
然后在allocator中对它们进行了调用,在这里,allocator作为一个工具类,其函数要定义为静态的,方便以allocator<T>::
的形式调用。
template <class T>
class allocator
{
public:
//...
static pointer allocate(size_type n, const void* hint=0)
{ return _allocate((difference_type)n, (pointer)0); }
static void deallocate(pointer p)
{ _deallocate(p); }
static void construct(pointer p, const T& value)
{ _construct(p, value); }
static void destory(pointer p)
{ _destory(p); }
}
allocator除了上面的几个函数,还有address()
和max_size()
,给出如下:
static pointer address(reference x)
{ return (pointer)&x; }
static const_pointer const_address(const_reference x)
{ return (const_pointer)&x; }
static size_type max_size()
{ return size_type(UINT_MAX / sizeof(T)); }
全部代码 Allocator.h
下面给出全部的代码
#ifndef ALLOCATOR_H
#define ALLOCATOR_H
#include <new> //for placement new
#include <cstddef> //for ptrdiff_t, size_t
#include <cstdlib> //for exit()
#include <climits> //for UNIT_MAX
#include <iostream> //for cerr
namespace LightSTL
{
template <class T>
inline T* _allocate(ptrdiff_t size, T*)
{
std::set_new_handler(0);
T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
if (tmp == 0)
{
std::cerr << "out of memeory" << std::endl;
exit(1);
}
return tmp;
}
template <class T>
inline void _deallocate(T* buffer)
{ ::operator delete(buffer);}
template <class T1, class T2>
inline void _construct(T1* p, const T2& value)
{ new(p) T1(value); } // placement new.invoke ctor of T1
template <class T>
inline void _destory(T* ptr)
{ ptr->~T(); }
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;
//rebind allocator of type U.
template <class U>
struct rebind
{ typedef allocator<U> other; };
//hint used for locality.
static pointer allocate(size_type n, const void* hint=0)
{ return _allocate((difference_type)n, (pointer)0); }
static void deallocate(pointer p)
{ _deallocate(p); }
static void construct(pointer p, const T& value)
{ _construct(p, value); }
static void destory(pointer p)
{ _destory(p); }
static pointer address(reference x)
{ return (pointer)&x; }
static const_pointer const_address(const_reference x)
{ return (const_pointer)&x; }
static size_type init_page_size()
{ return max(size_type(1), size_type(4096 / sizeof(T))); }
static size_type max_size()
{ return size_type(UINT_MAX / sizeof(T)); }
};
}//end of namespace
#endif // !ALLOCATOR_H
Test case:
int* ia = LightSTL::allocator<int>::allocate(1);
LightSTL::allocator<int>::construct(ia, 5);
std::cout << *ia << std::endl;
std::string* istr = LightSTL::allocator<std::string>::allocate(1);
LightSTL::allocator<std::string>::construct(istr, "abcd");
std::cout << *istr << std::endl;
//output
//5
//abcd