[C++]STL-空间配置器(一)
2016-05-18 19:16
363 查看
空间配置器
从STL的实现来说,首先需要明白的就是空间配置器,因为整个STL的操作对象都放在容器中,而容器需要一定配置空间以置放资料。空间配置器的标准接口
// 标准接口,一些typedef allocator::value_type; allocator::pointer; allocator::const_pointer; allocator::reference; allocator::const_reference; allocator::size_type; allocator::difference_type; allocator::rebind // 一个嵌套的class template,class rebind<U>拥有唯一成员other,那是一个typedef,代表allocator<U> allocator::allocator() // default constructor allocator::allocator(const allocator&) // copy constructor template <class U>allocator::allocator<const allocator<U>&) // generic copy constructor allocator::~allocator() // default constructor pointer allocator::address(reference x) const // 返回对象的地址,等同于&x const_pointer allocator::address(const_reference x)const // 返回对象的地址,等同于&x pointer allocator::allocate(size_type n, const void* = 0) // 配置空间,足以存储n个T对象,第二参数是个提示,实现上可能利用它来增进区域性,或完全忽略之 void allocator::deallocate(pinter p, size_type n) // 归还之前配置的空间 size_type allocator::max_size() const // 按返回可成功配置的最大空间 void allocator::constructor(pointer p, const T&x) // 等同于 new(const void* p ) T(x) 即placement new void allocator::destroy(pinter p) // 等同于 p->~T()
设计一个简单的空间配置器
#ifndef _Test_Alloc_ #define _Test_Alloc_ #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 Test_Alloc { 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 memory" << std::endl; } 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); } template <class T> inline void _destroy(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; template <class U> struct rebind { typedef allocator<U> other; }; pointer allocate(size_type n, const void* hint = 0) { return _allocate((difference_type)n, (pointer)0); } void deallocate(pointer p, size_type n) { _deallocate(p); } void construct(pointer p, const value_type& value) { _construct(p, value); } 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(T)); } }; } // end of namespace #endif #include <vector> int main() { int temp[5]{1, 2, 3, 4, 5}; std::vector<int, Test_Alloc::allocator<int>> iv(temp, temp+5); for (int i = 0; i < 5; ++i) { std::cout << iv[i] << std::endl; } return 0; }
(值得注意的是,在terminal中必须加上 -std=c++11 命令来使用C++11)
SGI STL使用一个专属的、拥有次层配置能力的,效率优越的特殊配置器,在稍后会提及。事实上,SGI STL仍然提供了一个标准的配置器接口,这个标准接口的配置器名为simple_alloc。
具备次配置力的SGI空间配置器
SGI STL的配置器与众不同,也与标准规范不同,其名称是alloc而非allocator,而且不接受任何参数。vector<int, std::alloc> iv;
在STL中,每个容器都指定了其缺省的空间配置器。
1. SGI特殊的空间配置器,std::alloc
一般而言,C++的内存配置操作和释放操作是调用new,delete。class Foo{...}; Foo* pf = new Foo; delete pf;
new实际包含两个操作
1)调用::operator new配置内存
2)调用Foo::Foo()构造对象内容。
delete也包含两个阶段:
1)调用析构函数
2)释放内存
STL allocator把这两个阶段分开,内存配置操作和释放由allocate() 和 deallocate()负责,对象构造和析构由construct()和destroy()负责。
2. 构造和析构工具:construct()和destroy()
以下给出 < stl _ construct.h > 的部分内容。#include <new> // placement new template <class T1, class T2> inline void construct(T1* p, const T2^ value) { new (p) T1(value); } template <class T> inline void destroy(T* pointer) { pointer->~T(); } // destroy带区间范围的版本 template <class ForwardIterator> inline void destroy(ForwardIterator first, ForwardIterator last) { __destroy(first, last, value_type(first)); } // 判读元素的数值类型是否有trivial destructor template <class ForwardIterator, class T> inline void __destroy(ForwardIterator first, ForwardIterator last, T*) { typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor; __destroy_aux(first, last, trivial_destructor()); } // 如果有trivial destructor template <class ForwardIterator, class T> inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __flase_type) { for ( ; first < last; ++first) { destroy(&*first); } } // 如果有trivial destructor,那么就什么都不做 template <class ForwardIterator, class T> inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __true_type) { } // 正对char*和wchar_t*的特化版本 inline void destroy(char*, char*) {} inline void destroy(wchar_t*, wchar_t* ) {}
值得注意的是,上述的constructor()接受一个指针p和一个初值value,该函数的用途是将初值设定到指针所指的空间上,也就是placement new运算子的作用。
而destroy则是接受指针,调用其析构函数。其中接受范围的版本需要特别注意,当范围非常大时,不断调用析构函数可能会导致效率的极具降低,所以此时我们需要判断析构函数是否有必须被调用的必要,(__type_traits),由此来区别两种不同的情况。至于如何判断析构函数是否为trivial,会在未来的文章中给出解释。
3. 空间的配置与释放 std::alloc
上文解释了内存配置后的对象构造和析构过程,现在我们来讨论内存的配置和释放。对象构造前的空间配置和对象析构后的空间释放,由< stl_alloc.h >负责,SGI对此的设计哲学为:
向system heap要求空间。
考虑多线程状态。
内存不足时的应变措施。
考虑过多“小型区块”可能造成的内存碎片问题。
(此处排除多线程的处理)
考虑到小型区块所可能造成的内存破碎问题,SGI设计了双层配置器,第一季配置器直接调用malloc() 和free()(也就是operator new 和 operator delete), 第二级配置器则视情况采用不同的策略:当配置区块大于128bytes,视之为“足够大”,便调用第一级配置器。当配置区块小于128bytes时,视为“过小”,为了降低额外负担,便采用复杂的memory pool整理方式,而不求助于第一级配置器。整个设计是否开放第二级配置器,取决于__USE_MALLOC是否被定义。
#ifdet __USE_MALLOC ... typedef __malloc_alloc_template<0> malloc_alloc; typedef malloc_alloc alloc; // 令alloc为第一级配置器 #else ... typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;// 令alloc为第二级配置器 #endif
尤其注意,alloc并不接受任何template型别参数。
无论alloc被定义为第一级或第二级配置器,SGI还为它再包装一个接口如下,使配置器的接口能够符合STL规格:
template<class T, class Alloc> class simple_alloc { public: static T* allocate(size_t n) { return 0 == n ? 0 : (T*)Alloc::aloocate(n * sizeof(T)); } static T* allocate(void) { return (T*) Alloc::allocate(sizeof(T)); } static void deallocate(T *p, size_t n) { if (0 != n) { Alloc::deallocate(p, n * sizeof(T)); } } static void deallocate(T *p) { Alloc::deallocate(p, sizeof(T)); } };
其内部的四个成员函数其实都是单纯的转调用,调用传递给配置器的成员函数,这个接口使配置器的配置单位从bytes转为个别元素的大小。
SGI STL容器都使用这种接口。
template <class T, class Alloc = alloc> class vector { protected: typedef simple_alloc<value_type, Alloc> data_allocator; void deallocate() { if (...) { data_allocator::deallocate(start, end_of_storage - start); } } ... };
一、二级配置器的关系,
接口包装,以及实际运用方式,见下图:
相关文章推荐
- C语言正则表达式详解 regcomp() regexec() regfree()详解
- 常见的几种内排序算法以及实现(C语言)
- C++对C的扩展之函数重载
- C语言模拟C++list
- C语言中结构体偏移及结构体成员变量访问方式的问题讨论
- C语言与ELF机器语言之间的关系
- C++走向远洋——51(数组类运算的实现)
- C++ 每周一些题(4)
- 【干货】国外程序员整理的 C++ 资源大全
- C++序列化方案
- 《C++精英内参之程序员高效指南》-12-1影响效率的不良习惯之打断
- 单链表的逆置
- 《C++精英内参之程序员高效指南》-12影响效率的不良习惯
- 《C++精英内参之程序员高效指南》-11常用的读代码方法除了写注释的,还有其他方法
- 《C++精英内参之程序员高效指南》-10如何快速读透代码
- 《C++精英内参之程序员高效指南》-9如何快速读透一本书1
- C++协程(1):协程原理及实现方式概述
- C++程序编译运行后窗口一闪而过问题
- C++回调
- c++易忘简单知识点