Allocator
是
C++
语言标准库中最神秘的部分之一。它们很少被显式使用,标准也没有明确出它们应该在什么时候被使用。今天的
allocator
与最初的
STL
建议非常不同,在此过程中还存在着另外两个设计--这两个都依赖于语言的一些特性,而直到最近才在很少的几个编译器上可用。对
allocator
的功能,标准似乎在一些方面追加了承诺,而在另外一些方面撤销了承诺。
这篇专栏文章将讨论你能用
allocator
来做什么以及如何定义一个自己的版本。我只会讨论
C++
标准所定义的
allocator
:引入准标准时代的设计,或试图绕过有缺陷的编译器,只会增加混乱。
1
什么时候不使用
Allocator
C++
标准中的
Allocator
分成两块:一个通用需求集(描述于
§ 20.1.5(
表
32)
),和叫
std::allocator
的
class
(描述于
§20.4.1
)。如果一个
class
满足表
32
的需求,我们就称它为一个
allocator
。
std::allocator
类满足那些需求,因此它是一个
allocator
。它是标准程序库中的唯一一个预先定义
allocator
类。
每个
C++
程序员都已经知道动态内存分配:写下
new X
来分配内存和创建一个
X
类型的新对象,写下
delete p
来销毁
p
所指的对象并归还其内存。你有理由认为
allocator
会使用
new
和
delete
--但它们没有。(
C++
标准将
::operator new()
描述为
“allocation function”
,但很奇怪,
allocator
并不是这样的。)
有关
allocator
的最重要的事实是它们只是为了一个目的:封装
STL
容器在内存管理上的低层细节。你不应该在自己的代码中直接调用
allocator
的成员函数,除非正在写一个自己的
STL
容器。你不应该试图使用
allocator
来实现
operator new[]
;这不是
allocator
该做的。
如果你不确定是否需要使用
allocator
,那就不要用。
allocator
是一个类
,
有着叫
allocate()
和
deallocate()
成员函数(相当于
malloc
和
free
)。它还有用于维护所分配的内存的辅助函数和指示如何使用这些内存的
typedef
(指针或引用类型的名字)。如果一个
STL
容器使用用户提供的
allocator
来分配它所需的所有内存
(
预定义的
STL
容器全都能这么做;他们都有一个模板参数,其默认值是
std::allocator)
,你就能通过提供自己的
allocator
来控制它的内存管理。
这种柔性是有限制的:仍然由容器自己决定它将要申请多少内存以及如何使用它们。在容器申请更多的内存时,你能控制它调用那个低层函数,但是你不能通过使用
allocator
来让一个
vector
行动起来像一个
deque
一样。虽然如此,有时候,这个受限的柔性也很有用。比如,假设你有一个特殊的
fast_allocator
,能快速分配和释放内存(也许通过放弃线程安全性,或使用一个小的局部堆),你能通过写下
std::list<T, fast_allocator<T> >
而不是简单的
std::list<T>
来让标准的
list
使用它。
如果这看起来对你很陌生,那就对了。没有理由在常规代码中使用
allocator
的。
2
定义一个
Allocator
关于
allocator
的这一点你已经看到了:它们是模板。
Allocator
,和容器一样,有
value_type
,而且
allocator
的
value_type
必须要匹配于使用它的容器的
value_type
。这有时会比较丑陋:
map
的
value_type
相当复杂,所以显式调用
allocator
的
map
看起来象这样的,
std::map<K
,
V, fast_allocator<std::pair<const K, V> > >
。
(
像往常一样,
typedef
会对此有帮助。
)
以一个简单的例子开始。根据
C++
标准,
std::allocator
构建在
::operator new()
上。如果你正在使用一个自动化内存使用跟踪工具,让
std::allocator
更简单些会更方便。我们可以用
malloc()
代替
:: operator new()
,而且我们也不考虑(在好的
std::allocator
实作中可以找到的)复杂的性能优化措施。我们将这个简单的
allocator
叫作
malloc_allocator
。
既然
malloc_allocator
的内存管理很简单,我们就能将重点集中在所有
STL
的
allocator
所共有的样板上。首先,一些类型:
allocator
是一个类模板,它的实例专为某个类型
T
分配内存。我们提供了一序列的
typedef
,以描述该如何使用此类型的对象:
value_type
指
T
本身,其它的则是有各种修饰字的指针和引用。
template <class T> class malloc_allocator
{
public:
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
...
};
这些类型与
STL
容器中的很相似,这不是巧合:容器类常常直接从它的
allocator
提取这些类型。
为什么有这么多的
typedef
?你可能认为
pointer
是多余的:它就是
value_type *
。绝大部份时候这是对的,但你可能有时候想定义非传统的
allocator
,它的
pointer
是一个
pointer-like
的
class
,或非标的厂商特定类型
value_type __far *
;
allocator
是为非标扩展提供的标准
hook
。不寻常的
pointer
类型也是存在
address()
成员函数的理由,它在
malloc_allocator
中只是
operator &()
的另外一种写法:
template <class T> class malloc_allocator
{
public:
pointer address(reference x) const { return &x; }
const_pointer address(const_reference x) const {
return &x;
}
...
};
现在我们能开始真正的工作:
allocate()
和
deallocate()
。它们很简单,但并不十分象
malloc()
和
free()
。我们传给
allocate()
两个参数:我们正在为其分派空间的对象的数目
(max_size
返回可能成功的最大请求值
)
,以及可选的一个地址值(可以被用作一个位置提示)。象
malloc_allocator
这样的简单的
allocator
没有利用那个提示,但为高性能而设计的
allocator
可能会利用它。返回值是一个指向内存块的指针,它足以容纳
n
个
value_type
类型的对象并有正确的对齐。
我们也传给
deallocate()
两个参数:当然一个是指针,但同样还有一个元素计数值。容器必须自己掌握大小信息;传给
allocate
和
deallocate
的大小参数必须匹配。同样,这个额外的参数是为效率而存在的,而同样,
malloc_allocator
不使用它。
template <class T> class malloc_allocator
{
public:
pointer allocate(size_type n, const_pointer = 0) {
void* p = std::malloc(n * sizeof(T));
if (!p)
throw std::bad_alloc();
return static_cast<pointer>(p);
}
void deallocate(pointer p, size_type) {
std::free(p);
}
size_type max_size() const {
return static_cast<size_type>(-1) / sizeof(value_type);
}
...
};
allocate ()
和
deallocate()
成员函数处理的是未初始化的内存,它们不构造和销毁对象。语句
a.allocate(1)
更象
malloc(sizeof (int))
而不是
new int
。在使用从
allocate()
获得的内存前,你必须在这块内存上创建对象;在通过
deallocate()
归还内存前,你需要销毁那些对象。
C+ +
语言提供一个机制以在特定的内存位置创建对象:
placement new
。如果你写下
new(p) T(a, b)
,那么你正在调用
T
的构造函数产生一个新的对象,一如你写的
new T(a, b)
或
T t(a, b)
。区别是当你写
new(p) T(a, b)
,你指定了对象被创建的位置:
p
所指向的地址。
(
自然,
p
必须指向一块足够大的内存,而且必须是未被使用的内存;你不能在相同的地址构建两个不同的对象。
)
。你也可以调用对象的析构函数,而不释放内存,只要写
p->~T()
。
这些特性很少被使用,因为通常内存的分配和初始化是一起进行的:使用未初始化的内存是不方便的和危险的。你需要如此低层的技巧的很少几处之一就是你在写一个容器类,于是
allocator
将内存的分配与初始化解耦。成员函数
construct()
调用
placement new
,而且成员函数
destory()
调用析构函数。
template <class T> class malloc_allocator
{
public:
void construct(pointer p, const value_type& x) {
new(p) value_type(x);
}
void destroy(pointer p) { p->~value_type(); }
...
};
(
为什么
allocator
有那些成员函数,什么时候容器可以直接使用
placement new
?一个理由是要隐藏笨拙的语法,而另一个是如果写一个更复杂的
allocator
时你可能想在构造和销毁对象时
construct()
和
destroy()
还有其它一些副作用。比如,
allocator
可能维护一个所有当前活动对象的日志。
)
这些成员函数没有一个是
static
的,因此,容器在使用
allocator
前做的第一件事就是创建一个
allocator
对象--也就是说我们应该定义一些构造函数。但是,我们不需要赋值运算:一旦容器创建了它的
allocator
,这个
allocator
就从没想过会被改变。表
32
中的对
allocator
的需求没有包括赋值。只是基于安全,为了确保没人偶然使用了赋值运算,我们将禁止掉这个可能自动生成的函数。
template <class T> class malloc_allocator
{
public:
malloc_allocator() {}
malloc_allocator(const malloc_allocator&) {}
~malloc_allocator() {}
private:
void operator=(const malloc_allocator&);
...
};
这些构造函数实际上没有做任何事,因为这个
allocator
不需要初始化任何成员变量。基于同样的理由,任意两个
malloc_allocator
都是可互换的;如果
a1
和
a2
的类型都是
malloc_allocator<int>
,我们可以自由地通过
a1
来
allocate()
内存然后通过
a2
来
deallocate()
它。我们因此定义一个比较操作以表明所有的
malloc_allocator
对象是等价的:
template <class T>
inline bool operator==(const malloc_allocator<T>&,
const malloc_allocator<T>&) {
return true;
}
tmplate <class T>
inline bool operator!=(const malloc_allocator<T>&,
const malloc_allocator<T>&) {
return false;
}
你会期望一个
allocator
,它的不同对象是不可替换的吗?当然--但很难提供一个简单而有用的例子。一种明显的可能性是内存池。它对大型的
C
程序很常见,从几个不同的位置(
“
池
”
)分配内存,而不是什么东西都直接使用
malloc()
。这样做有几个好处,其一是
it only takes a single function call to reclaim all of the memory associated with a particular phase of the program
。使用内存池的程序可能定义诸如
mempool_Alloc
和
mempool_Free
这样的工具函数,
mempol_Alloc(n, p)
从池
p
中分配
n
个字节。很容易写出一个
mmepool_alocator
以匹配这样的架构:每个
mempool_allocator
对象有一个成员变量以指明它绑定在哪个池上,而
mempool_allocator::allocate()
将调用
mempool_Alloc()
从相应的池中获取内存。
[
注
1]
最后,我们到了
allocator
的定义体中一个微妙的部份:在不同的类型之间映射。问题是,一个
allocator
类,比如
malloc_allocator<int>
,全部是围绕着单个
value_type
构建的:
malloc_allocator< int>::pointer
是
int*
,
malloc_allocator<int>().allocate(1)
返回足够容纳一个
int
对象的内存,等等。然而,通常,容器类使用
malloc_allocator
可能必须处理超过一个类型。比如,一个
list
类,不分配
int
对象;实际上,它分配
list node
对象。
(
我们将在下一段落研究细节。
)
于是,当你创建一个
std::list<int, malloc_allocator<int> >
时,
list
必须将
malloc_allocator<int>
转变成为处理
list_node
类型的
malloc_allocator
。
这个机制称为重绑定,它有二个部份。首先,对于给定的一个
value_type
是
X1
的
allocator
类型
A1
,你必须能够写出一个
allocator
类型
A2
,它与
A1
完全相同,除了
value_type
是
X2
。其次,对于给定的
A1
类型的对象
a1
,你必须能够创建一个等价的
A2
类型对象
a2
。这两部分都使用了成员模板,这也就是
allocator
不能被老的编译器支持,或支持得很差的原因。
template <class T> class malloc_allocator
{
public:
template <class X>
malloc_allocator(const malloc_allocator<X>&) {}
template <class X>
struct rebind { typedef malloc_allocator<X> other; };
...
};
这其实意味着一个
allocator
类不能仅仅是单个类;它必须是一族相关类,每个都有它自己的
value_type
。一个
allocator
类必须要有一个
rebind
成员,因为它使得从一个类变成同族的另外一个类成为可能。
如果有一个
allocator
类型
A1
,对应于另外一个
value_type
的类型是
typename A1::template rebind<X2>::othere[
注
2]
。正如你能将一个类型转换为另外一个,模板的转换用构造函数让你转换值:你可以写
malloc_allocator<int>(a)
,无论
a
的类型是
malloc_allocator<int>
,或
malloc_allocator<double>
,或
malloc_allocator<string>
。象往常一样,
malloc_allocator
的转换用构造函数用不着做任何事,因为
malloc_allocator
没有成员变量。
附带一提,虽然绝大多数的
allocator
有一个模板参数
(allocator
的
value_type)
,但这不是需求中的规定,只不过常常正巧如此。重绑定机制在多模板参数的
allocator
上也工作良好:
template <class T, int flags> class my_allocator
{
public:
template <class U>
struct rebind { typedef my_allocator<U, flags> other; };
...
};
最后,最后一个细节:对于
void
我们该怎么做?有时一个容器必须涉及
void
的指针
(
再一次,我们将会在下一段落研究细节
)
,而重绑定机制几乎给了我们所需要的东西,但并不完全。它不能工作,因为我们会写出类似
malloc_allocator<void>::pointer
的代码,而我们定义的
malloc_allocator
用
void
实例化时是非法的。它使用了
sizeof(T)
,和涉及了
T &
;当
T
是
void
时,这两个都是非法的。解决的方法如同问题本身一样简单:为
void
特化
malloc_allocator
,扔掉其它一切,只留下我们需要的
void
的指针。
template<> class malloc_allocator<void>
{
typedef void value_type;
typedef void* pointer;
typedef const void* const_pointer;
template <class U>
struct rebind { typedef malloc_allocator<U> other; };
};
就是它了!
malloc_allocator
的完备源码见
Listing 1
。
3
使用
Allocator
使用
allocator
的最简单方法,当然是将它们作为参数传给容器类;用
std::vector<char, malloc_allocator<char> > V;
取代简单的
std::vector<char>
,或用
typedef std::list<int, mempool_allocator<int> > List;
List L(mempool_allocator<int>(p));
取代简单的
std::list<int>
。
但是你能做得更多。
STL
的卖点是它是可扩展性:正如你能写自己的
allocator
,你也能写你自己的容器类。如果你很小心
,
并且你写的容器类使用它的
allocator
来处理所有的内存相关操作,那么别人将能够加入他们自己的用户自定义
allocator
。
诸如
std::vector
和
std::list
这样的容器类是很复杂的,而且大部分复杂性与内存管理无关。让我们以一个简单的例子开始,这样我们可以只关注于
allocator
。考虑一个固定大小数组的类,
Array
,元素的个数是在构造函数中设定的,并且在此之后不会改变。
(
这有点像
std:: valarray
的一个简化版本。
)
它有二个模板参数,元素类型和
allocator
类型。
容器,和
allocator
一样,以巢式类型申明开始:
value_type
,
reference
,
const_reference
,
size_type
,
difference_type
,
iterator
,和
const_iterator
。通常,这些类型中的绝大部分都可以直接从它的
allocator
中获得--这也解释了为什么容器的
value_type
必须和
allocator
中的相匹配。
当然,
iterator
类型通常不来自于
allocator
;通常
iterator
是一个类,完全取决于容器的内在表示。
Array
类比通常见到的容器简单,因为它实际上将所有元素存储在单块连续内存中;我们只要维护指向内存块开始和结束处的两个指针。
iterator
就是指针。
在更进一步之前,我们必须决定:我们将怎样储存
allocator
?构造函数将接受一个
allocator
对象作为参数。我们必须在容器的整个生命期内保存它的一个拷贝,因为在析构函数中还需要它。
依感觉,这儿没什么问题:我们只要申明一个
Allocator
类型的成员变量,然后使用它。方法是正确的,但不爽。毕竟,在
99
%的时间里,用户都不想考虑有关
allocator
的事;他们只会写
Array<int>
并使用默认值--而默认的
allocator
可能是一个没有任何非
static
成员变量的空类。问题是即使
Allocator
是一个空类,这样的成员变量也会有开销。
(
这是
C++
标准所要求的。
)
我们的
Array
类将会有三个
word
的开销,而不是两个。也许一个
word
的额外开销不是大问题,但总是不爽,它迫使所有用户为一个几乎从不使用的功能承担了开销。
都很多方法来解决这个问题,其中一些使用了
traits
类和偏特化。或许最简单的解决方法就是使用(私有)继承而不是成员变量。编译器被允许优化掉空基类,而且时下绝大多数的编译器都这么做了。
我们最终能写下定义的骨架了:
template <class T, class Allocator = std::allocator<T> >
class Array : private Allocator
{
public:
typedef T value_type;
typedef typename Allocator::reference reference;
typedef typename Allocator::const_reference
const_reference;
typedef typename Allocator::size_type size_type;
typedef typename Allocator::difference_type
difference_type;
typedef typename Allocator::pointer iterator;
typedef typename Allocator::const_pointer const_iterator;
typedef Allocator allocator_type;
allocator_type get_allocator() const {
return static_cast<const Allocator&>(*this);
}
iterator begin() { return first; }
iterator end() { return last; }
const_iterator begin() const { return first; }
const_iterator end() const { return last; }
Array(size_type n = 0,
const T& x = T(),
const Allocator& a = Allocator());
Array(const Array&);
~Array();
Array& operator=(const Array&);
private:
typename Allocator::pointer first;
typename Allocator::pointer last;
};
如果要满足对容器的需求的话
(
需求全集见于
C++
标准
§23.1
,
Table 65)
,这还不是我们所要的全部样板,但是绝大部分都和
allocator
完全无关。对于我们的目的而言,最感兴趣的成员函数是构造函数(它分配内存和创建对象),和析构函数(它销毁内存并释放内存)。
构造函数初始化
allocator
基类,获得足够容纳
n
个元素的一块内存
(
如果我们正在写类似于
vector
的东西,我们可能申请更大些的内存以供增长用
)
,然后遍历内存以创建初始化值的拷贝。唯一的问题是异常安全:如果某一个元素的构造函数抛出了一个异常,我们必须撤销我们所做的一切。
template <class T, class Allocator>
Array<T, Allocator>::Array(size_type n,
const T& x,
const Allocator& a)
: Allocator(a), first(0), last(0)
{
if (n != 0) {
first = Allocator::allocate(n);
size_type i;
try {
for (i = 0; i < n; ++i) {
Allocator::construct(first + i, x);
}
}
catch(...) {
for(size_type j = 0; j < i; ++j) {
Allocator::destroy(first + j);
}
Allocator::deallocate(first, n);
throw;
}
}
}
(
你可能会问为什么我们手写了这个循环;
std::uninitialized_fill()
不是已经做我们所需要的东西?几乎,但不完全相同。我们必须调用
allocator
的构造成员函数而不是简单的
pacement new
。也许未来的
C++
标准将会包含一个接受
allocator
为参数的
uninitialized_fill()
函数,从而使得不再需要这个显式循环。
析构函数比较简单,因为我们不需要担心异常安全:
T
的析构函数被假设为从不抛出异常。
template <class T, class Allocator>
Array<T, Allocator>::~Array()
{
if (first != last) {
for (iterator i = first; i < last; ++i)
Allocator::destroy(i);
Allocator::deallocate(first, last - first);
}
}
我们这个简单的
array
类不需要使用重绑定或转换,但这只是因为
Array<T, Allocator>
绝不产生
T
类型以外的对象。当你定义更复杂的数据结构时,其它类型就出现了。比如,考虑一下
value_type
是
T
的链表类
list
。链表通常是由节点组成的,每个节点含有一个
T
类型的对象和一个指向下个节点的指针。于是,作为最初的尝试,我们可能定义链表的节点为:
template <class T>
struct List_node
{
T val;
List_node* next;
};
把一个新的值加入
list
的过程看起来可能是这样的:
l
使用一个
value_type
是
List_node<T>
的
allocator
,为一个新节点分配内存。
l
使用一个
value_type
是
T
的
allocator
,在节点的
val
位置上构建新的元素。
l
将这个节点连接到适当的位置。
这个过程需要处理两个不同的
allocator
,其中一个是通过对另一个的重绑定而获得的。它对几乎所有程序都工作得很好,即使是将
allocator
用于相当复杂的目的的程序。它所不能完成的是向
allocator
提供一些不寻常的指针类型。它明显依赖于能使用
List_node<T> *
类型的普通指针。
如果你极端野心勃勃,想将其它可能的指针类型传给
allocator
,一切都突然变得复杂多了--从一个节点指向另一个节点的指针不再是
List_node<T> *
或
void *
了,但它必须是能够从
allocator
获取的某个类型。直接实现是不太可能的:用一个不完全的类型实例化
allocator
是非法的,因此,除非
List_node
已经被完整定义,否则无法谈论指向
List_node
的指针。我们需要一个精巧的申明顺序。
template <class T, class Pointer>
struct List_node
{
T val;
Pointer next;
};
template <class T, class Alloc>
class List : private Alloc
{
private:
typedef typename Alloc::template rebind<void>::other
Void_alloc;
typedef typename Void_alloc::pointer Voidptr;
typedef typename List_node<T, Voidptr> Node;
typedef typename Alloc::template rebind<Node>::other
Node_alloc;
typedef typename Node_alloc::pointer Nodeptr;
typedef typename Alloc::template rebind<Voidptr>::other
Voidptr_alloc;
Nodeptr new_node(const T& x, Nodeptr next) {
Alloc a = get_allocator();
Nodeptr p = Node_alloc(a).allocate(1);
try {
a.construct(p->val, x);
}
catch(...) {
Node_alloc(a).deallocate(p, 1);
throw;
}
Voidptr_alloc(a).construct(p->next, Voidptr(next));
return p;
}
...
};
最后,以防你认为这么费劲才获得这么小的好处太不值了,提醒一下:仅仅因为你能写一个使用
allocator
的容器,并不意谓你必须,或你应该。有时你可能写一个依赖于特殊的内存分配策略的容器类,比如复杂到基于磁盘的
B
树容器或简单到我的书中所描述的
block
类。即使你确实想写一个使用
allocator
的容器类,你也不是必须支持可能的指针类型。你可以写一个容器,而要求所有用户自定义的
allocator
使用普通指针,并在文档中明确说明这个限制。不是每件事都要完全通用。展望未来
如果你想写一个如同
malloc_allocator
这样简单的
allocator
,应该没有困难。
(
前提是你正在使用一个比较现代的编译器
)
。然而,如果你的心比较大,--基于内存池的或支持非标指针类型的
allocator
--情况就比较不令人满意了。
如果你想使用可能的类指针(
pointer-like
)类型,它必须支持哪些操作?它必须有一个
null
值吗?如果是的话,那个值应该如何书写?你能使用类型转换吗?你如何在类指针对象与普通指针间进行转换?你必须要考虑操作指针时抛的异常吗?我在这最后一节提出了一些假设;
C++
标准没有指明这些假设的对错。这些细节都留给了具体的标准库的实现,即使某个实现完全忽略可选指针类型也是合法的。
C++
标准还遗留了一些没有答案的问题,比如当一个
allocator
的不同实例不可互换时,将发生什么。
幸运的是,情形并不象标准
(§ 20.1.5,
第
4-5
段
)
中的词汇所描述得那么可怕。标准留下没有答案的问题,是因为在制订标准时,
C++
标准化委员会对答案达不成共识;对
allocator
的必要的经验还不存在。每个参与制订的人都认为这是一个临时的补丁,其中的含糊肯定将在将来的修订中被去除。
等一下,如果你关心可移植性,最好还是远离可选的指针类型,而,如果你乐意接受一些限制,就能安全地使用诸如
mempool_allocator
这样不同对象实体间差别很大的
allocator
。所有主流标准库的实现现在都在某个方面不支持这样的
allocator
,而且不同实现间的差别不大。
正如容器接受一个
allocator
作为模板参数,容器的构造函数也接受一个
allocator
作为参数。容器保留这个参数的一份拷贝,并使用这个拷贝来处理所有的内存管理;容器的
allocator
一旦在构造函数中初始化完成,就终生不变。
唯一的问题是当你运行一个需要两个容器在内存管理上进行协同的操作时,会发生什么。在标准库中确实存在两个这样的操作:
swap()
(所有容器)和
std::list::splice()
。原理上,可以以这样几个不同的方式处理他们:
l
禁止在两个
allocator
不等价的容器间进行
swap()
和
splice()
。
l
在
swap()
和
splice()
中放入一个
allocator
等价性的测试。如果不等价,降格调用
copy()
和赋值运算。
l
只提供
swap()
:如同对容器中的数据一样,交换它们的
allocator
。
(
很难找出如何将它推广到
splice
上
.
它同样也带来一个问题:如何
swap
没有定义赋值运算符的东西?
)
如果你能避免对可能具有不同
allocator
的容器使用
swap()
和
splice()
,一切都很安全。在实践中,我没有发现这会造成严重束缚:你需要严格练习安全使用诸如内存池这类特性,而你或许并不想不加选择地混用使用不同
allocator
的容器。
部分因为不熟悉,部分因为
C++
标准的要求并不令人满意,目前对
allocator
的绝大多数的使用都很简单。由于
C++
社群对
allocator
变得越来越熟悉,并且标准已被阐明,我们能期待更复杂的使用将会浮现。
Listing 1: A sample allocator based on malloc
template <class T> class malloc_allocator
{
public:
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
template <class U>
struct rebind { typedef malloc_allocator<U> other; };
malloc_allocator() {}
malloc_allocator(const malloc_allocator&) {}
template <class U>
malloc_allocator(const malloc_allocator<U>&) {}
~malloc_allocator() {}
pointer address(reference x) const { return &x; }
const_pointer address(const_reference x) const {
return x;
}
pointer allocate(size_type n, const_pointer = 0) {
void* p = std::malloc(n * sizeof(T));
if (!p)
throw std::bad_alloc();
return static_cast<pointer>(p);
}
void deallocate(pointer p, size_type) { std::free(p); }
size_type max_size() const {
return static_cast<size_type>(-1) / sizeof(T);
}
void construct(pointer p, const value_type& x) {
new(p) value_type(x);
}
void destroy(pointer p) { p->~value_type(); }
private:
void operator=(const malloc_allocator&);
};
template<> class malloc_allocator<void>
{
typedef void value_type;
typedef void* pointer;
typedef const void* const_pointer;
template <class U>
struct rebind { typedef malloc_allocator<U> other; };
};
template <class T>
inline bool operator==(const malloc_allocator<T>&,
const malloc_allocator<T>&) {
return true;
}
template <class T>
inline bool operator!=(const malloc_allocator<T>&,
const malloc_allocator<T>&) {
return false;
}