当分配一大块内存时,我们通常计划在这块内存上按需构造对象,这样的我们希望将内存分配和对象构造分离。但是通常的new关键字的分配的动态空间,有时候会造成一些浪费,更致命的是“如果一个类没有默认构造函数,那么这个类就不能动态分配数组了”。
这时我们需要一个能将内存分配和对象构造分离的机制,allocator很好地帮助我们解决了这个问题。
#include 《memory》,allocator提供了一种类型感知的内存分配方法,它分配的内存是原始的,未构造的。我们可以利用allocator提供的操作对这些内存进行操作,
allocator<T> a | 定义了一个名为a的allocator对象,它可以为类型为T的对象分配内存 |
a.allocator(n) | 先定义,后分配。这分配一段原始的,未构造的,保存n个类型为T的对象;----》可以看出alllocator方法分配内存,也是先定义类型,在分配类型的数量 |
a.deallocat(p,n) |
note,在此操作之前,必须堆每个在这个内存中创建的对象调用destory方法。 释放从T*,这块内存保存了n类型为T的对象,p必须是一个先前由allocator返回的指针.且n必须是p创建时所要求大小,在调用deallocator之前,用于必须堆每个在这块内存中创建的对象调用destroy方法 |
a.construct(p,args) | p必须是一个T*的指针,指向一个块原始内存,args被传递给类型为T的构造函数,用在在p指向的内存中构造一个对象 |
a.destroy(p) | p为类型为T*的指针,此方法堆p所指对象执行析构函数 |
allocator<string> alloc; auto const p = alloc.allcoate(n); auto q = p; alloc.construct(q++);//*q is a null string alloc.construct(q++,10,'c')// *q is cccccccccc alloc.construct(q++,"hi")//*q is hi 在未构造对象的情况下,就使用原始内存是错误的 例如 cout<<*p<<endl; cout<<*q<<endl;//这个是非法的 记住,为了使用allcocate返回的内存,我们必须用construct构造对象,使用未构造的函数,其行为是未定义的. 党我们使用完对象后,必须堆每个构造函数调用destroy来销毁 while(q!=p){ alloc.destroy(--q); }//我们只能堆真正构造了的元素进行destroy操作
//一旦元素被销毁后,我们重新使用这部分内存来保存其他string,
//也可以将内存归还给os,释放内存是通过deallocate(p,n)来完成的
alloc.deallocate(p,n);
拷贝/填充未初始化的算法,STL还定义了两个伴随算法,可以在未初始化内存中创建对象
uninitialized_copy(b,e,b2) | 从迭代器b和e指出的输入范围拷贝元素到迭代器b2指定的未构造的原始原始内存中,b2指向的内存必须足够大,能容纳输入序列中元素的拷贝 |
uninitiated_copy(b,n,b2) | |
uninitiated_fill(b,e,t) |
在迭代器b和e指定的原始内存范围中创建对象,对象的值均为t的拷贝 |
uninitiated_fill(b,n,t) |
/* 假定有一个例子,希望将其内容拷贝到动态内存中,我们将分配一块比vector中元素空间大一倍的动态内存,然后将原vector中的元素拷贝到前一半空间,后一半空间利用一个给定值进行填充 */ auto p = alloc.allocate(vi.size()*2); auto q = uninitiated_copy(vi.begin(),vi.end(),p); uninitiated_fill(q,vi.size(),42);
test case
1 #ifndef STRVEC_H_INCLUDED 2 #define STRVEC_H_INCLUDED 3 #include<vector> 4 #include<string> 5 #include<memory> 6 #include<utility> 7 #include<iostream> 8 /** 9 这里要实现的是一个STLvector的简化版,只能运用于string,由于没有template. 10 设计思路: 11 1,预先分配足够大的内存来保存可能需要的更多的元素, 12 2,当StrVec添加每个元素时,成员函数chk_n_alloc()会检查是否有足够空间容纳更多的元素 13 2.1 如果有,alloc.construct()会在下一个可用位置构造一个对象 14 2.2 如果没有,StrVec会充分分配空间alloc_n_copy(),并且拷贝一个给定范围中的元素,然后释放掉旧的空间free() 15 并添加新的元素 16 17 18 --------------------------------------------- 19 | | | | | | 20 --------------------------------------------- 21 ^ ^ ^ 22 elements first_free cap 23 -- 24 每个StrVec有三个指针指向其元素所使用的内存: 25 elements,指向分配中的首元素 26 frist_free,指向最后一个实际元素位置之后的位置 27 cap,指向分配的内存末尾之后的位置 28 ------------------ 29 30 StrVec除了指出位置的指针外,还有名为alloc的静态成员,类型为allocator<string>,它分配我们所需要的内存 31 --- 32 另外还有4个工具函数, 33 alloc_n_copy(),分配内存并拷贝一个给定范围中的元素 34 free()//销毁构造的元素病释放内存 35 chk_n_alloc()//保证StrVec至少能容纳一个新元素的空间,如果没有空间添加新元素,chk_n_alloc会调用reallocate()来分配更多的空间按 36 reallocate()//在内存使用完后,reallocate()重新分配元素 37 38 */ 39 40 41 42 using namespace std; 43 class StrVec{ 44 public: 45 StrVec():elements(nullptr),first_free(nullptr),cap(nullptr){}///构造函数,对成员进行初始化 46 StrVec(const StrVec&);///拷贝构造函数,从无到有 47 StrVec &operator=(const StrVec &);///拷贝赋值运算符=,对存在的元素赋予新值 48 ~StrVec();///析构函数 49 50 void push_back(const std::string&);///拷贝元素 51 52 size_t size() const {return first_free - elements;}///返回StrVec中的元素个数 53 size_t capacity() const {return cap - elements;}///返回StrVec中实际分配的(可以容纳类型元素的)内存大小 54 string *begin() const {return elements;}///返回首元素 55 string *end() const {return first_free;}///返回StrVec末尾元素的后一个位置 56 57 58 private: 59 static allocator<string> alloc;///分配元素,注意是静态成员 60 void chk_n_alloc(){///被添加元素的函数使用 61 if(size() == capacity()) 62 reallocate(); 63 }/// 64 ///工具函数,被拷贝构造函数,赋值运算符和析构函数使用 65 pair<string*,string*> alloc_n_copy(const string*,const string*);///什么时候没有空间容纳新元素呢? first_free==cap的时候. 66 void free();///撤销元素病释放内存 67 void reallocate();///获得更多内存并且拷贝已有元素 68 69 string *elements;///指向数组首元素的指针 70 string *first_free;///指向数组第一个空闲元素的指针 71 string *cap;///指向数组尾后位置的指针 72 }; 73 74 void show(); 75 76 #endif // STRVEC_H_INCLUDED
----
1 #include "StrVec.h" 2 using namespace std; 3 4 allocator<string> StrVec::alloc; 5 6 void show(){ 7 std::cout<<"from StrVec.cpp"<<std::endl; 8 } 9 10 void StrVec::push_back(const string& s){ 11 chk_n_alloc();/// 12 13 alloc.construct(first_free++,s); 14 } 15 16 17 ///函数返回一个pair,两个指针分别指向新空间的开始位置和拷贝的尾后位置,即data是新空间开始的位置,uninitiated_copy()返回新空间拷贝的尾后位置 18 pair<string*, string*> 19 StrVec::alloc_n_copy(const string *b,const string *e){ 20 auto data = alloc.allocate(e-b); 21 return make_pair(data,uninitialized_copy(b,e,data)); 22 //return {data,unintialized_copy(b,e,data)}; 23 } 24 25 void StrVec::free(){ 26 ///can not pass a nullptr to deallocate(),if elements is 0,then free() do nothing 27 if(elements){ 28 for(auto p = first_free;p != elements;/* */){ 29 StrVec::alloc.destroy(--p); 30 } 31 StrVec::alloc.deallocate(elements,cap-elements); 32 } 33 } 34 35 StrVec::StrVec(const StrVec &s){ 36 auto newdata = alloc_n_copy(s.begin(),s.end()); 37 elements = newdata.first; 38 first_free = newdata.second; 39 cap = newdata.second; 40 } 41 42 StrVec::~StrVec(){ 43 free(); 44 } 45 46 StrVec &StrVec::operator=(const StrVec &rhs){ 47 /// 48 auto data = alloc_n_copy(rhs.begin(),rhs.end()); 49 free();///释放掉已有元素之前,调用alloc_n_copy缓存rhs的元素,这可以处理自我赋值 50 elements = data.first; 51 first_free = data.second; 52 cap = data.second; 53 54 return *this;/// 55 } 56 57 /** 58 在重新分配内存的过程中移动而不是拷贝元素 59 我们reallocate()应该做些什么呢? 60 1,为一个新的,更大的string数组分配元素 61 2,在内存空间的前一部分构造,保存现有元素 62 3,销毁原来内存空间中的元素,并释放这块内存 63 */ 64 65 void StrVec::reallocate(){ 66 auto newcapacity = size() ? 2*size():1; 67 68 auto newdata = StrVec::alloc.allocate(newcapacity); 69 70 auto dest = newdata; 71 auto elem = elements; 72 73 for(size_t i = 0;i != size();i++){ 74 StrVec::alloc.construct(dest++,std::move(*elem++)); 75 } 76 77 free(); 78 79 elements = newdata; 80 first_free = dest; 81 cap = elements + newcapacity; 82 }
-----main.cpp
#include <iostream> #include <vector> #include <algorithm> #include <map> #include <set> #include <unordered_set> #include <unordered_map> #include "StrVec.h" using namespace std; int main() { show(); StrVec sv; sv.push_back("aaa"); sv.push_back("bbb"); int length = sv.size(); string *ptr = sv.begin(); for(int i = 0;i<length;i++){ cout<<*ptr++<<endl; } StrVec svb(sv); string *ptrb = svb.begin(); for(int i = 0;i<svb.size();i++){ cout<<*ptrb++<<endl; } StrVec svc = sv; string *ptrc = svb.begin(); for(int i = 0;i<svc.size();i++){ cout<<*ptrc++<<endl; } return 0; }
================