C++内存管理变革(7):基于ScopeAlloc的STL容器
2008-02-04 15:03
691 查看
C++内存管理变革(7):基于ScopeAlloc的STL容器
许式伟2008-2-4
来由
在前文(请参阅《C++内存管理变革(6):通用型垃圾回收器 - ScopeAlloc》),我们介绍了ScopeAlloc。既然我们称之为一个通用型的GC Allocator,那么这里我们就谈谈如何用ScopeAlloc来改造STL的容器,它们包括:std::list, std::map, std::set, std::multimap, std::multiset。改造方法
不改变std::list, std::map, std::set, std::multimap, std::multiset等STL容器的用法,仅仅将其Allocator换成ScopeAlloc。做法不难想到,这需要我们提供一个GC Allocator到STL的Allocator的Wrapper类。它就是我们的StlAlloc类。StlAlloc
代码参见:<stdext/Memory.h>)
template <class _Ty, class _Alloc = ScopeAlloc> class StlAlloc { private: _Alloc* m_alloc; public: typedef size_t size_type; typedef ptrdiff_t difference_type; typedef _Ty* pointer; typedef const _Ty* const_pointer; typedef _Ty& reference; typedef const _Ty& const_reference; typedef _Ty value_type; public: pointer address(reference val) const { return &val; } const_pointer address(const_reference val) const { return &val; } size_type max_size() const { size_type count = (size_type)(-1) / sizeof (_Ty); return (0 < count ? count : 1); } public: StlAlloc(_Alloc& alloc) : m_alloc(&alloc) {} pointer allocate(size_type count, const void*) { return m_alloc->allocate(count * sizeof(_Ty)); } void deallocate(void* p, size_type cb) { m_alloc->deallocate(p, cb); } void construct(pointer p, const _Ty& val) { new(p) _Ty(val); } void destroy(pointer p) { p->~_Ty(); } public: char* _Charalloc(size_type cb) { return (char*)m_alloc->allocate(cb); } };
关于StlAlloc,并没有特别重要的细节需要交代。只有一个细节,是非常有意思的,就是Visual C++ 6.0为allocator引入了一个特殊的函数:_Charalloc。这个函数的功能类似于malloc,用于分配一块不确定企图的普通内存块(请注意,实际上allocator的本意要避免使用这样的方法,allocator分配的内存应该是签名的,或者说带类型信息的)。
再谈STL的Allocator
实际上,STL的Allocator推荐使用rebind技巧解决“同一个STL容器需要分配多种类型的对象”问题。关于allocator详细规格和rebind技术的细节,请参阅《CUJ:标准库:Allocator能做什么?》。Visual C++ 6.0版本带的STL引入了特殊的_Charalloc函数,其目的是规避标准中要求STL allocator实现的rebind而已。不同版本的STL,其allocator规格存在不一致性。例如:
Visual C++ 6.0之STL库引入了特殊的_Charalloc。
SGI STL有自己的alloc组件,和我们这里的StlAlloc一样,SGI STL的std::allocator只是自己的alloc组件的一个Wrapper。并且默认情形下使用alloc组件,而不是std:: allocator。这说明SGI STL的设计者并不喜欢标准的allocator。
这种不一致性的本质原因是,STL的allocator存在一定的设计缺陷。为了更加“C++”,设计者试图去让allocator分配的内存是签 名的(或者说带类型信息的)。但是,签名意味着它不是通用的内存管理组件,无法取代new/delete, malloc/free的地位。为了避免暴露类的实现细节,_Charalloc这样的东西的存在就在所难免了(rebind技术是繁琐且丑陋的)。
改造后的STL容器
改造后的STL容器,我们命名为std::List, std::Map, std::Set, std::MultiMap, std::MultiSet。其源码请参考:<stdext/List.h>
<stdext/Map.h>
<stdext/Set.h>
实际上我们除了重写了构造函数,并没有改变任何东西。以Map类为例,改造的代码如下:
template < class KeyT, class DataT, class PredT = std::less<KeyT>, class AllocT = ScopeAlloc > class Map : public std::map< KeyT, DataT, PredT, StlAlloc<DataT, AllocT> > { private: typedef StlAlloc<DataT, AllocT> _Alloc; typedef std::map<KeyT, DataT, PredT, _Alloc> _Base; public: explicit Map(AllocT& alloc, const PredT& pred = PredT()) : _Base(pred, alloc) { } template <class Iterator> Map(AllocT& alloc, Iterator first, Iterator last, const PredT& pred = PredT()) : _Base(first, last, pred, alloc) { } };
使用改造后的STL容器
一个简单的样例如下:typedef std::Map<int, int> MapT; std::BlockPool recycle; std::ScopeAlloc alloc(recycle); MapT coll(alloc); coll.insert(MapT::value_type(1, 2)); coll.insert(MapT::value_type(1, 4)); coll.insert(MapT::value_type(2, 4)); coll.insert(MapT::value_type(4, 8));
实际上,也可以使用AutoFreeAlloc作为Allocator,如下:
typedef std::Map<int, int, std::less<int>, std::AutoFreeAlloc> MapT; std::AutoFreeAlloc alloc; MapT coll(alloc); coll.insert(MapT::value_type(1, 2)); coll.insert(MapT::value_type(1, 4)); coll.insert(MapT::value_type(2, 4)); coll.insert(MapT::value_type(4, 8));
特别提醒:
改造后的STL容器的性能评估
虽然结果是不言而喻的,但也许你仍然关心关于改造后的STL容器性能的具体数据。这一小结就进行简单对比测试。如下:《各种内存分配器在STL容器上的性能对比》
结论是:
使用ScopeAlloc或者AutoFreeAlloc后,STL容器的性能有显著提高。而ScopeAlloc与AutoFreeAlloc在该容器存在大量内存分配时区别不显著。另外,由于内存池技术的作用,ScopeAlloc在Share同一个BlockPool后,性能略有改善。
为什么没有std::vector等容器?
也许你很奇怪,既然ScopeAlloc可以改善STL容器的性能,为什么我们不将它应用到所有的STL容器。其实,问题在于其实那些容器它并不需要ScopeAlloc。这些容器包括:std::vector
std::basic_string, std::string, std::wstring
std::deque, std::stack, std::queue
无论是AutoFreeAlloc,还是ScopeAlloc,以及其他所有号称可以改善内存分配性能的Allocator, 仅仅适用于小内存管理。对于那些本身是线性内存结构的(如:std::vector, std::basic_string, std::string, std::wstring等),或者已经采用MemBlock(内存块)结构的(如:std::deque, std::stack, std::queue等),并不需要他们。这些容器并不需要一个特别的Allocator,普通的new/delete足矣。
相关文章推荐
- C++内存管理变革(7):基于ScopeAlloc的STL容器
- C++内存管理变革(6):通用型垃圾回收器 - ScopeAlloc
- C++内存管理变革(6):通用型垃圾回收器 - ScopeAlloc
- 【C++】STL常用容器总结之六:基于deque的顺序容器适配器
- C++内存管理变革(6):通用型垃圾回收器 - ScopeAlloc
- C++ 浅析 STL 中的 list 容器
- 【C++ STL学习之四】容器list深入学习
- C++STL模板容器(一)
- C++ STL 基本容器使用
- c++ stl容器set成员函数介绍及set集合插入,遍历等用法举例
- C++ STL入门教程(1)——vector(向量容器)的使用(附完整程序代码)
- C++ STL模板与容器 知识 初学 小结 ( 一 )
- C++STL中vector容器 begin()与end()函数、front()与back()的用法
- C++STL选择合适的容器以及容器操作的时间复杂度
- C++内存管理变革(2):最袖珍的垃圾回收器
- [C++杂记] STL容器当作参数传递
- C++中STL容器的find的应用
- C++ STL — 第6章 STL容器(二)deque
- C++内存管理变革(3):另类内存管理
- c++ stl容器set成员函数介绍及set集合插入,遍历等用法举例