这里以自定义的vector类为例,谈谈为什么需要空间配置器。
自定义实现一个简单的vector
![image]()
这里定义了三个成员变量,分别是_first(vector起始地址),_last(vector实际存放的末尾元素地址),_end(vector开辟的空间地址)。
使用模板类封装实现了vector,实现了以下函数:
- vector构造函数,使用new开辟内存+构造对象
- 析构函数,使用delete删除内存
- 拷贝构造函数
- 赋值构造函数
- push_back()函数,从vector最后插入
- pop_back()函数,从vector最后弹出
- back(),获取末尾元素
- empty(),判断是否为空
- full(),判断是否满
- size(),获取元素大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| template<typename T> class vector { public: vector(int size = 5) { _first = new T[size]; _last = _first; _end = _first + size; } ~vector() { delete[]_first; _first = _last = _end = nullptr; } vector(const vector<T>& cp) { int size = cp._end - cp._first; _first = new T[size]; int len = cp._last - cp._first; for (int i = 0; i < len; i++) { _first[i] = cp._first[i]; } _last = _first + len; _end = _first + size; }
vector<T>& operator=(const vector<T>& cp) { if (this == &cp) return*this; delete[]_first; int size = cp._end - cp._first; _first = new T[size]; int len = cp._last - cp._first; for (int i = 0; i < len; i++) { _first[i] = cp._first[i]; } _last = _first + len; _end = _first + size; return *this; } void push_back(const T& val) { if (full()) expand(); *_last++ = val; } void pop_back() { if (empty()) return; --_last; } T back() const { if (empty()) throw "stack is empty"; return *(_last - 1); } bool empty() const{ return _first == _last; } bool full() const { return _last == _end;} int size() const { return _last - _first; } private: T* _first; T* _last; T* _end;
void expand() { int size = _end - _first; T* ptmp = new T[2 * size]; for (int i = 0; i < size; i++) { ptmp[i] = _first[i]; } delete[]_first; _first = ptmp; _last = _first + size; _end = _first + 2 * size; } };
|
可以实现vector简单的插入、弹出、扩容等操作。
运行下面的代码,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Test { public: Test() { cout << "Test()" << endl; } ~Test() { cout << "~Test()" << endl; } Test(const Test&) { cout << "拷贝构造" << endl; }
Test& operator= (const Test& cp) { cout << "赋值构造" << endl; return *this; } };
int main() { Test t1, t2, t3; vector<Test> vec; cout << vec.size() << endl; vec.push_back(t1); vec.push_back(t2); vec.push_back(t3); cout << vec.size() << endl; vec.pop_back(); cout << vec.size() << endl; return 0; }
|
![image]()
存在以下问题:
1.在初始化的时候,开辟内存和对象构造是一起执行的;但是有些时候我只是为了声明变量(如vector<Test> vec
,在这里只是为了声明定义以Test
类的vec
数组,但是上图是开辟内存+构造对象),这样就会带来没必要的开销(声明时构造对象,出作用域时析构对象);
2.上述实现的vector的pop_back是采用的指针回退策略,但如果该指针指向的test对象指向了外部资源,单纯的指针回退会导致这一部分资源无法管理,导致空间泄露
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Test { public: Test() { _ps=new int[5]; } ~Test() { delete []_ps; _ps=nullptr; } private: int *_ps; };
|
这里定义的Test类中是指向了外部对象的(_ps=new int[5];
),如果仅仅是指针回退,而不析构对应的对象的话,会造成管理的内存资源泄露。
空间配置器实现
所以很自然的就是想到,将
内存的开辟和对象的构造分开;
内存的释放和对象的析构分开。
而空间配置器就是为了实现这样的功能,具体为,空间配置器实现了几个比较重要的函数:
allocate()
,负责内存的开辟
deallocate()
,负责内存的释放
construct()
,负责对象的构造
destroy()
,负责对象的析构
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
template <typename T> struct Allocator { T* allocate(size_t size) { return (T*)malloc(sizeof(T) * size); }
void deallocate(void* p) { free(p); }
void construct(T* p, const T& val) { new (p) T(val); }
void destroy(T* p) { p->~T(); } };
|
这样做的好处有,在声明对象的时候只是开辟内存空间,而不进行无意义的默认对象构造;并且在删除资源的时候,先执行对象的析构函数,确保对象指向的外部资源被释放后,才清除当前堆内存,步骤如下。
![image]()
实现空间配置器后的vector
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
|
template<typename T,typename Alloc = Allocator<T>> class vector { public: vector(int size = 10) { _first = _allocator.allocate(size); _last = _first; _end = _first + size; } ~vector() { for (T* p = _first; p != _last; ++p) { _allocator.destroy(p); } _allocator.deallocate(_first); _first = _last = _end = nullptr; } vector(const vector<T>& cp) { int size = cp._end - cp._first; _first = _allocator.allocate(size); int len = cp._last - cp._first; for (int i = 0; i < len; i++) { _allocator.construct(_first + i, cp._first[i]); } _last = _first + len; _end = _first + size; }
vector<T>& operator=(const vector<T>& cp) { if (this == &cp) return*this;
for (T* p = _first; p != _last; ++p) { _allocator.destroy(p); } _allocator.deallocate(_first);
int size = cp._end - cp._first; _first = _allocator.allocate(size); int len = cp._last - cp._first; for (int i = 0; i < len; i++) { _allocator.construct(_first + i, cp._first[i]); } _last = _first + len; _end = _first + size; return *this; } void push_back(const T& val) { if (full()) expand(); _allocator.construct(_last, val); _last++; } void pop_back() { if (empty()) return; --_last; _allocator.destroy(_last); } T back() const { if (empty()) throw "stack is empty"; return *(_last - 1); } T& operator [] (int index) { if (index < 0 || index >= size()) throw "index out of range Exception"; return _first[index]; } bool empty() const { return _first == _last; } bool full() const { return _last == _end; } int size() const { return _last - _first; } private: T* _first; T* _last; T* _end; Alloc _allocator;
void expand() { int size = _end - _first; T* ptmp = _allocator.allocate(2 * size); for (int i = 0; i < size; i++) { _allocator.construct(ptmp+i, _first[i]); _allocator.destroy(_first+i); } _allocator.deallocate(_first); _first = ptmp; _last = _first + size; _end = _first + 2 * size; } };
|
![image]()
从上图可以看到,实现了自定义的空间配置器之后,在定义vector<Test> vec;
时就不会出现没必要的对象构造了。
关于空间配置器的作用就先讲解到这了,空间配置器的实现可以参考。
5 千字长文+ 30 张图解 | 陪你手撕 STL 空间配置器源码