我是照着侯捷老师的《STL源码剖析》做的cghSTL,现在才看到第三章,忽然觉得这本书的编排非常适合自学。
源码下载:github
第一章讲解空间配置器,这是STL最基础的部件,没什么好说的。
第二章讲解迭代器,老师举了单向链表的例子,这非常有考究,单向链表不需要连续的存储空间,意味着使用单向链表实现的容器不需要考虑空间溢出等问题,我们可以把注意力集中在容器的迭代器上。
第三章讲解序列式容器,一来说怎么实现vector,vector需要考虑空间的连续使用和空间溢出等问题,正因为vector维护的是连续线性空间,所以不论其元素类型为何,普通指针都可以作为vector的迭代器而满足所有必要条件!这成功的避开了设计容器时不得不分散精力设计迭代器的坑!
到此,我觉得侯捷老师写书真是有水平啊!
言归正传,接下来我们看看一个vector的实现细节。
我们一共有四个文件:
1. globalConstruct.h,构造和析构函数文件
2. cghAlloc.h,空间配置器文件
3. cghVector.h,容器的实现文件
4. cghSTL.cpp,测试文件
先看第一个,globalConstruct.h构造函数文件
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 功能:全局构造和析构的实现代码 ******************************************************************/ #include "stdafx.h" #include<new.h> #include<type_traits> #ifndef_CGH_GLOBAL_CONSTRUCT_ #define_CGH_GLOBAL_CONSTRUCT_ namespace CGH { #pragma region 统一的构造析构函数 template<class T1, class T2> inline void construct(T1* p, constT2& value) { new (p)T1(value); } template<class T> inline void destroy(T* pointer) { pointer->~T(); } template<class ForwardIterator> inline void destroy(ForwardIteratorfirst, ForwardIterator last) { // 本来在这里要使用特性萃取机(traits编程技巧)判断元素是否为non-trivial // non-trivial的元素可以直接释放内存 // trivial的元素要做调用析构函数,然后释放内存 for (; first < last;++first) destroy(&*first); } #pragma endregion } #endif
按照STL的接口规范,正确的顺序是先分配内存然后构造元素。构造函数的实现采用placement new的方式;为了简化起见,我直接调用析构函数来销毁元素,而在考虑效率的情况下一般会先判断元素是否为non-trivial类型,non-trivial的元素可以直接释放内存,可以理解为non-trivial全部分配在栈上,不用我们操心,由程序自动回收,trivial的元素要分配在堆上,需要手工调用析构函数来销毁,然后释放内存。
cghAlloc.h是空间配置器文件,空间配置器不仅要负责内存的申请和回收,也负责对象的构造和析构,故globalConstruct.h包含在cghAlloc.h中。
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 功能:cghAllocator空间配置器的实现代码 ******************************************************************/ #include <new> #include <cstddef> #include <cstdlib> #include <climits> #include <iostream> #ifndef_CGH_ALLOC_ #define _CGH_ALLOC_ namespace CGH { #pragma region 内存分配和释放函数、元素的构造和析构函数 // 内存分配 template<class T> inline T* _allocate(ptrdiff_t size, T*) { set_new_handler(0); T* tmp = (T*)(::operator new((size_t)(size* sizeof(T)))); if (tmp == 0) { std::cerr << "outof memory" << 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); } // 元素析构 template<class T> inline void _destroy(T* ptr) { ptr->~T(); } #pragma endregion #pragma region cghAllocator空间配置器的实现 template<class T> class cghAllocator { 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 cghAllocator<U>other; }; static pointer allocate(size_typen, const void* hint = 0) { return _allocate((difference_type)n,(pointer)0); } static void deallocate(pointerp, size_type n) { _deallocate(p); } void construct(pointer p, constT& value) { _construct(p, value); } void destroy(pointer p) { _destroy(p); } pointer address(reference x) { return (pointer)&x; } const_pointer const_address(const_referencex) { return (const_pointer)&x; } size_type max_size() const { return size_type(UINT_MAX/ sizeof(T)); } }; #pragma endregion #pragma region 封装STL标准的空间配置器接口 template<class T, class Alloc = cghAllocator<T>> class simple_alloc { public: static T* allocate(size_t n) { return 0 == n ? 0 : (T*)Alloc::allocate(n*sizeof(T)); } static T* allocate(void) { return (T*)Alloc::allocate(sizeof(T)); } static void deallocate(T* p, size_tn) { if (0 != n)Alloc::deallocate(p,n*sizeof(T)); } static void deallocate(T* p) { Alloc::deallocate(p,sizeof(T)); } }; #pragma endregion } #endif
class cghAllocator是空间配置器类的定义,主要的四个函数的意义如下:allocate函数分配内存,deallocate函数释放内存,construct构造元素,destroy析构元素。这四个函数最终都是通过调用_allocate、_deallocate、_construct、_destroy这四个内联函数实现功能。
我们自己写的空间配置器必须封装一层STL的标准接口,
template<classT, class Alloc = cghAllocator<T>> class simple_alloc
vector的实现代码如下:
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 功能:cghVector容器的实现代码 ******************************************************************/ #include "globalConstruct.h" #include "cghAlloc.h" #include <memory> #ifndef_CGH_VECTOR_ #define _CGH_VECTOR_ namespace CGH { template<class T, class Alloc = cghAllocator<T>> class cghVector { public: typedef T value_type; typedef value_type* pointer; typedef value_type* iterator; typedef value_type& reference; typedef size_t size_type; typedef ptrdiff_t difference_type; //typedef __false_type has_trivial_default_constructor; //typedef __false_type has_trivial_copy_destructor; //typedef __false_type has_trivial_assignment_operator; //typedef __false_type has_trivial_destructor; //typedef __false_type is_POD_type; protected: typedef simple_alloc<value_type,Alloc> data_allocator; // 定义空间配置器 iterator start; iterator finish; iterator end_of_storage; void insert_aux(iterator position,const T& value) { if (finish != end_of_storage) { construct(finish,*(finish - 1)); ++finish; T x_copy = value; std::copy_backward(position,finish - 2, finish - 1); *position =x_copy; } else { ptrdiff_t old_size= size(); const size_typelen = old_size != 0 ? 2 * old_size : 1; /* 配置原则:如果原大小为0,则配置1个元素大小 如果原大小不为0,则配置原大小的两倍 */ iterator new_start= data_allocator::allocate(len); iterator new_finish= new_start; try { //把 start 到 position 这段内存拷贝到 new_start 处,返回 new_finish = new_start + ( position - start ) new_finish= std::uninitialized_copy(start, position, new_start); construct(new_finish,value); // 在 new_finish 处构造新元素 ++new_finish; //new_finish= std::uninitialized_copy(position, finish, new_finish); } catch (std::exceptionex) { //如果执行失败就要回滚 destroy(new_start,new_finish); data_allocator::deallocate(new_start,len); throw; } destroy(begin(),end()); deallocate(); start = new_start; finish = new_finish; end_of_storage= new_start + len; } } #pragma region 释放内存和析构元素 void deallocate() { if (start) { data_allocator::deallocate(start,end_of_storage - start); } } #pragma endregion #pragma region 分配内存和构造元素 /* * fill_initialize和allocate_and_fill把cghVector的初始化分为了两步: * 1.fill_initialize的职责是分配一段内存 * 2.fill_initialize调用allocate_and_fill,在分配的内存中调用构造函数创建cghVector的元素 */ void fill_initialize(size_typen, const T& value) { start = allocate_and_fill(n,value); finish = start + n; end_of_storage = finish; } iterator allocate_and_fill(size_typen, const T& x) { iterator result = data_allocator::allocate(n); iterator cur = result; for (; n > 0; --n,++cur) { construct(&*cur,x); } return result; } #pragma endregion public: #pragma region 对cghVector的读操作 iterator begin(){ return start;} // 返回cghVector头元素的地址 iterator end(){ return finish;} // 返回cghVector尾元素的地址 size_type size(){ return size_type(int(end()- begin())); } // cghVector的长度 = 尾元素地址 - 头元素地址 size_type capacity()const{ returnsize_type(end_of_storage - start); } // cghVector的容量 = 最大容量地址 - 头元素地址 bool empty()const { return begin()== end(); } // cghVector是否为空:头元素地址是否等于 尾元素地址 reference operator[](size_typen){ return *(begin() + n); } // 返回指定位置的元素引用 reference front(){ return *begin();} // 获得头元素位职 reference back(){ return *(end()- 1); } // 获得尾元素位置 #pragma endregion #pragma region 构造函数 cghVector() :start(0), finish(0),end_of_storage(0) { } // 初始化空的cghVector cghVector(size_type n, const T&value){ fill_initialize(n, value); } // 初始化包含n个值为value的cghVector cghVector(int n, const T&value){ fill_initialize(n, value); } // 同上 cghVector(long n, const T&value){ fill_initialize(n, value); } // 同上 explicit cghVector(size_type n){fill_initialize(n, T()); } // 初始化cghVector的长度为n #pragma endregion #pragma region 析构函数 ~cghVector() { destroy(start, finish);// 先调用cghVector中元素的析构函数 deallocate(); // 再释放cghVector占用的内存 } #pragma endregion #pragma region 对cghVector的写操作 /** * 弹出尾元素 */ void pop_back() { --finish; destroy(finish); } /** * 在cghVector末尾插入一个元素 */ void push_back(const T& x) { // 判断cghVector的容量是否满了,如果没满我们直接在已有的内存区域上构造元素 if (finish != end_of_storage) { construct(finish,x); ++finish; } else // 如果满了我们就要重新分配内存并重新构造函数 { insert_aux(end(),x); } } /** * 清除[first, last)区间内的元素 */ iterator erase(iterator first,iterator last) { // 把last到finish这段元素拷贝以first作为起始处的内存空间,返回first+ ( finish - last ) 的地址 iterator i = std::copy(last,finish, first); destroy(i, finish); //销毁(first + ( finish - last ), finish]这段内存 finish = finish - (last- first); // 重新设置finish return first; } /** * 清除某个位置上的元素 */ iterator erase(iterator position) { if (position + 1 != end()) { copy(position+ 1, finish, position); } --finish; destroy(finish); return position; } #pragma endregion }; } #endif
注释已经写得足够详细。 cghVector.h中调用了三个标准全局函数:std::copy_backward、std::uninitialized_copy、std::copy,这三个函数。
测试代码:
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 文件名称:cghVector容器的测试代码 ******************************************************************/ #include "stdafx.h" #include "cghAlloc.h" #include "globalConstruct.h" #include "cghVector.h" using namespace::std; int _tmain(int argc,_TCHAR* argv[]) { using namespace::CGH; cghVector<int> test(2, 2); cout << "size = " <<test.size() << "\t"; cout << "capacity = " <<test.capacity() << endl; test.push_back(1); cout << "size = " <<test.size() << "\t"; cout << "capacity = " <<test.capacity() << endl; test.push_back(2); cout << "size = " <<test.size() << "\t"; cout << "capacity = " <<test.capacity() << endl; test.push_back(3); cout << "size = " <<test.size() << "\t"; cout << "capacity = " <<test.capacity() << endl; test.pop_back(); cout << "size = " <<test.size() << "\t"; cout << "capacity = " <<test.capacity() << endl; std::cout << test[2] << endl;// 返回指定位置处的元素,这里体现了vector迭代器的随机访问性质(random access iterators) test.erase(test.begin() + 1, test.begin()+ 2); test.erase(test.begin()); for (cghVector<int>::iterator it =test.begin(); it != test.end(); it++) { std::cout << *it <<std::endl; } system("pause"); return 0; }
可以看到当cghVector的大小(size)超过容量(capacity)时,cghVector自动扩容,每次扩充为原来的2倍。