关于STL Allocator

stl allocator源码学习 概述 介绍几个allocator的源码实现:简单的对operator new和operator delete进行封装的实现,vs2015中的实现,STLport中的实现,仿造STLport实现内存池。 1. 参考 http://www.cplusplus.com/reference/memory/allocator/ 《STL源码剖析》 《C++ Primer 第五版》 《Generi 阅读详情

STL Allocator
Allocator是C++语言标准库中最神秘的部分之一。它们很少被显式使用,标准也没有明确出它们应该在什么时候被使用。今天的 allocator与最初的STL建议非常不同,在此过程中还存在着另外两个设计--这两个都依赖于语言的一些特性,而直到最近才在很少的几个编译器上可 用。对allocator的功能,标准似乎在一些方面追加了承诺,而在另外一些方面撤销了承诺。

这篇专栏文章将讨论你能用allocator来做什么以及如何定义一个自己的版本。我只会讨论C++标准所定义的allocator:引入准标准时代的设计,或试图绕过有缺陷的编译器,只会增加混乱。

什么时候不使用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的。

定义一个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;
}

template <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 U>
malloc_allocator(const malloc_allocator<U>&) {}

template <class U>
struct rebind { typedef malloc_allocator<U> other; };

...
};

这其实意味着一个allocator类不能仅仅是单个类;它必须是一族相关类,每个都有它自己的value_type。一个allocator类必须要有一个rebind成员,因为它使得从一个类变成同族的另外一个类成为可能。

如果有一个allocator类型A1,对应于另外一个value_type的类型是typename A1::template rebind<X2>::othere2。 正如你能将一个类型转换为另外一个,模板的转换用构造函数让你转换值:你可以写 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。

使用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的过程看起来可能是这样的:

  • 使用一个value_type是List_node<T>的allocator,为一个新节点分配内存。
  • 使用一个value_type是T的allocator,在节点的val位置上构建新的元素。
  • 将这个节点连接到适当的位置。

这个过程需要处理两个不同的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()。原理上,可以以这样几个不同的方式处理他们:

  • 禁止在两个allocator不等价的容器间进行swap()和splice()。
  • 在swap()和splice()中放入一个allocator等价性的测试。如果不等价,降格调用copy()和赋值运算。
  • 只提供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;
}

 

 原文地址 http://http://cplusplus.wikidot.com/cn:allocator

实现自定义的 STL allocator STL allocator 的注释讲解请见:http://blog.youkuaiyun.com/lifesider/archive/2011/06/06/6527776.aspx 这里实现的自定义allocator是继承自std::allocator,因为两者有公共部分,对于完全重写的allocator,下面的内容就不用读了。 上源码#include templateclas 阅读详情

相关推荐

STL 空间配置器 allocator

`STL` 空间配置器 `allocator``STL` 标准下的 `allocator`神奇的 `SGI STL``SGI` 里效率低下的 `std::allocator``SGI` 里正规的 `std::alloc`构造 `construct()` & 析构 `destroy()``alloc` 设计哲学`alloc` 的第一级配置器:`__malloc_alloc_template``alloc` 的第二级配置器:`__default_alloc_template` 无私奉献的 alloca

weixin_46323890的博客 655

一篇文章搞懂STL中的空间配置器allocator(原创,多图,易懂)

Table of Contents 0.引入 1.标准的空间配置器allocator 2.更为高效的空间配置器alloc 2.1----对象的构造与析构 2.1.1 对象的构造:::construct(); 2.1.2对象的析构:::destroy(); 2.2----内存的配置与释放 2.2.0 概述 2.2.1一级配置器:__malloc_alloc_template 2...

vjhghjghj的博客 4816

实现自己的STL配置器allocator

实现自己的STL配置器 STL所有数据都存储在容器之中,而容器需要配置空间以置放资料,所以配置器就是完成这一个任务的。 以下是它的一些接口 allocator::value_type //类型,如int allocator::pointer //指针,如int * allocator::const_pointer allocator::reference allocator::const_...

c++ 708

C++: STL内存分配器--allocator

**STL内存分配器--allocator** 一、STL内存分配器二、STL allocator 一、STL内存分配器 分配器(allocator))是C ++标准库的一个组件, 主要用来处理所有给定容器(vector,list,map等)内存的分配和释放。C ++标准库提供了默认使用的通用分配器std::allocator,但开发者可以自定义分配器。 GNU STL除了提供默认分配器,还提供了__pool_alloc、__mt_alloc、array_allocator、malloc_allocat

好好学习,天天向上~ 6205

8.5/Q1,Charls最新高分文章解读!

8 257名受试者中,672人(8.14%)在9年内发生过卒中。在调整的多因素cox回归模型中,TyG和ABSI仍然是卒中的预测因素,TyG的HR为1.25(95%CI:1.06~1.47),ABSI的HR为1.33(95%CI:1.06~1.68)。TyG和ABSI联合诊断效能最高(AUC:0.579,95%CI:0.553~0.597),而TyG(AUC:0.566,95%CI:0.544~0.587)和ABSI(AUC:0.533,95%CI:0.524~0.597)的诊断效能最低。

gsky_sci的博客 1015

解构LOL数值平衡:从基础公式到实战决策的深层逻辑

本文深入解析了《英雄联盟》数值平衡的底层逻辑,从基础攻击力公式到经济系统设计,揭示了MOBA游戏背后的精密数学架构。通过实战案例展示护甲减伤、技能伤害建模等核心机制,探讨装备定价与战斗节奏的数值关联,帮助玩家理解版本更新中的平衡艺术。

weixin_42540248的博客 347

浅析STL allocator

转载自 一般而言,我们习惯的 C++ 内存配置操作和释放操作是这样的: 1 class FOO{}; 2 FOO *pf = new FOO; 3 delete pf; 我们看其中第二行和第三行,虽然都是只有一句,当是都完成了两个动作。但你 new 一个对象的时候两个动作是:先调用::operator new 分配一个对象大小的内存,然后在这个内存上调用FOO::FOO()构造对象。同样...

teslali 3000

Dify 第 4 篇 Dify 示例:数据库执行Agent

本文介绍了如何在Dify平台创建一个能够执行SQL查询的数据库Agent。通过安装并配置"通义千问"和"数据库"两个插件,实现自然语言到SQL语句的转换和执行。关键步骤包括:1)安装插件并配置API Key;2)设置默认数据库连接(建议使用只读账号);3)准备测试数据表结构。该Agent支持通过工具调用查询表结构、转换自然语言为SQL、执行查询并返回结果,同时提供了CSV查询和HTTP API接口功能。文中还提供了MySQL测试数据表的创建脚本和示例数据。

甘蓝的专栏 1157

空间配置器(allocator)详解-stl源码剖析学习笔记

空间配置器也就是配置空间,配置容器所需要的空间,该空间获取可以是内存,也可以是磁盘或其他存储介质。

weixin_42853463的博客 1083

8、音频运行时系统的架构设计

本文深入探讨了音频运行时系统的分层设计及其优势,包括逻辑层和物理层的功能划分。通过分层设计,开发者可以更轻松地管理和优化音频资源,确保其在不同平台上的高效运行。文章还介绍了平台适配与优化、错误处理与调试以及未来发展方向等内容,为开发者提供了宝贵的参考和指导。

juice的博客 463

【异常】Error loading ASGI app. Could not import module ‘main‘

出现或。

奶爸的编程之路,也就一周冷个三天 1855

STL Allocator 内存分配全揭秘

STL 容器都需要传入一个内存分配器 allocator,如果不指定,则使用默认 std::allocator 内存分配器。std::allocator 旨在将内存分配和对象构造。本文基于 gcc-12.2 分析了 std::allocater 的实现。另外,也分析了对象拷贝函数std::copy() 和 std::fill() 函数的实现。

谢谢大家的关注 1853

RTK_ROS_导航(4):接入move_base

现在我们要将之前的数据接入到Move_base中,使得机器人能够根据Map上的终点进行导航,并且实现避障效果如下图。

旋风小短腿 1947

关于STL allocator

关于STL 中allocator的接口与实现,C++标准有比较清楚的定义:allocator wiki 1. SGI STL版本的allocator并没有遵守C++标准。它只提供simple_alloc类共container使用,设计的allocator名字叫做alloc,有二级配置器。第一级配置采用malloc/free来实现allocate/deallocate,第二级配置器采用针对

zeroom的技术专栏 8454

【Agent实战】Agent Skills 深度解析:从原理到落地的完整指南

本文深入解析了Agent Skills的概念及其在AI协作中的实际应用。Skill是一种可被AI按需发现和加载的任务执行说明书,它将团队经验、规则和步骤固化为结构化文档,如SKILL.md。文章阐明了Skill与Prompt、MCP、Function Calling的关系,强调Skill专注于"怎么做"而非底层能力。重点介绍了SKILL.md的编写规范,包括元数据命名规则、描述技巧和正文内容优化,旨在帮助开发者高效构建可复用的知识资产,提升Agent的任务执行稳定性与专业性。通过清晰的示例和对比,为AI架构

少说,多做 116

一分钟了解 Claude Code

在 AI 编程工具百花齐放的 2026 年,凭借其终端原生、全项目理解、自主执行的核心能力,开辟了全新赛道。它不是要替代你的 IDE 或编辑器,而是成为你最强大的终端扩展,让你在熟悉的环境中,用自然语言驱动开发,专注创意,而非琐事。

h a 6 6 6 c k 为 你解决复杂难题! 2207

深入理解 STL allocator

由于std::set,std::multiset,std::map,std::multimap四种容器的插入删除操作性能高并且自动排序,在很多时候比如需要动态操作时往往会使用它们,然后由于容器内部使用的是节点,每次的插入或删除都要调用new或delete,往往容易造成碎片和性能下降,于是自定义的allocator出现了。这篇文章的目的就是详细讲解std::allocator的内部结构,为实现自

lifesider 1万+

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

STL中的空间配置器allocator的实现原理及源码剖析

allocator是STL为自己的模版容器所设计的标准空间配置器。处于std命名空间下 今天我要讲的的allocator的源码在我的github上,地址为 mySTL 是我模仿标准STL写出的代码,并添加了注释。allocator简介allocator为一个模版类,我们使用的时候一般都会将其绑定到一个具体的类型上。如下:allocator<int> alc;allocator大致需要实现如下功能:

我的大学 4092

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

STL 配置器(allocator)

配置器:负责空间配置与管理,从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的 class template。 空间配置器:整个 STL 的操作对象(所有的数值)都存放在容器之内,而容器一定需要配置空间以存放内容。 什么是 allocator?allocator 有什么用? 我们需要对 C++ 的 allocator 的堆内存接口调用顺序有个清晰的认识,如下图所示。 allocator 堆内存管理接口 STL 的容器(eg: vector、stack、deque等)有一个共同特征,就是

南宫封清的博客 1353

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

C++ STL——allocator

C++ 标准模板库提供了一个非常重要的内存管理机制——allocator。它对内存管理接口进行了封装,实现了对象内存分配与构造分离、对象析构与内存释放分离。标准库中通过模板参数为容器提供内存管理策略,allocator 就是一种具体的策略,我们可以实现基于共享内存、内存池的 自定义 allocator 把它们应用于容器。本文章将以 MSVC 的 allocator 框架为参考,讲述 allocator 的核心工作原理。

ypy9323的博客 597

stl allocator

stl allocator实现

sjc960115的博客 547

续STL之allocator

针对CPP的标准模版库的空间配置器而写的文章,通过实现一个自己的配置器了解容器底层空间操作原理

thirtythird的博客 525

STL中的Allocator

最近在看STL,写一点博客。 关于一些细节可能就不放上来了,就写一些我自己的心得。 1.为什么要用Allocator C++容器的设计思路是,对存放到里面的东西是拷贝一份放进去。 而且里面的空间都是动态分配的。 所以在构建容器的时候,需要必要的①内存分配②构造对象。 2.new operator、operator new、placement new

山鸡哥的专栏 1367

[STL]源码分析:allocator

一、STL规定的allocator标准 参见侯捷《STL源码剖析》STL规定的allocator标准包含如下功能(方法),当然,并非所有的STL实现版本都严格遵循此标准,因为allocator被STL隐藏,用户一般不直接使用allocator,故而与标准不同也不影响STL的使用。 allocator::value_type allocator::pointer allocator::con

1559
上一篇: [经验总结]MFC各种控件属性介绍(转载)
下一篇: [经验总结]如何在VC中去掉STL代码编译时编译错误
ThinkHY
博客等级 码龄19年 168粉丝 247原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值