C++ Primer第十三章⑤
C++ Primer
拷贝控制
动态内存管理类
某些类需要在运行时分配可变大小的内存空间,这种类最好在底层使用标准容器库,例如我们的StrBlob类使用vector。
但是,有些类需要自己进行内存分配,它基本就要定义自己的拷贝控制成员来管理所分配的内存。
我们在这一节将要实现标准库vector类的一个简化版本,它只能用于string,命名为StrVec。
StrVec类的设计
主要参照vector<string>来。
我们将使用allocator来获得原始内存,由于它分配的内存是未构造的,我们将需要在添加新元素是用allocator的construct成员在原始内存中创建对象;同样的,我们在删除元素时就使用destroy成员来销毁函数。
每个StrVec有三个指针成员指向其元素所使用的内存:
- elements,指向首元素
- first_free,尾后元素
- cap,指向分配的内存末尾之后的位置
StrVec还有一个名为alloc的静态成员,其类型为allocator<string>。alloc成员会分配StrVec使用的内存。我们的类还有4个工具函数:
- alloc_n_copy会分配内存,并拷贝一个给定范围内的元素
- free会销毁构造的元素并释放内存
- chk_n_alloc保证StrVec至少有容纳一个新元素的空间,如果空间不够的话,它会调用reallocate来分配更多内存
- reallocate在内存用完时为StrVec分配新内存
StrVec类定义
设计好了之后,我们就可以动手写类了(有些函数只有声明,还没实现定义,后面再实现):
class StrVec
{
public:
StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr){} //默认构造函数
//我看着这个函数还一时没反应过来(可能因为头晕。。。)
StrVec(const StrVec&); //拷贝构造函数声明
StrVec &operator=(const StrVec&); //拷贝赋值运算符声明
~StrVec(); //析构函数
//一些常用成员函数
void push_back(const string&);
size_t size() const {return first_free - elements;}
size_t capacity() const {return cap - elements;}
string *begin() const {return elements;}
string *end() const {return first_free;}
private:
static allocator<string> alloc; //分配元素
void chk_n_alloc()
{
if(size() == capacity())
{
reallocate();
}
}
pair<string*, string*> alloc_n_copy(const string*, const string*);
void free();
void reallocate();
//数据成员
string *elements;
string *first_free;
string *cap;
};
该说明的都已经在注释中说明了。
接下来分别取实现已经声明的函数定义:
push_back
void StrVec::push_back(const string& s)
{
chk_n_alloc(); //确保有空间
alloc.construct(first_free++, s); //调用allocator成员construct来插入
//至于construct怎么搞得咱们就不了解了,有兴趣自己去查把
}
alloc_n_copy
分配足够的内存来保存给定范围的元素,并将这些元素拷贝到新分配的内存中:
pair<string*, string*> StrVec::alloc_n_copy(const string *b, const string *e)
{
auto data = alloc.allocate(e-b); //分配正好的空间
return {data, uninitialized_copy(b, e, data)};
}
在返回语句中完成拷贝工作:
返回语句中对返回值进行了列表初始化,返回的pair中,first指向分配内存的开始位置(因为data作为名字来用就是首元素地址吧),second是uninitialized_copy的返回值,这个值是一个指向尾后元素的指针。
free
free要干两件事:
- destroy元素,是指确实有内容的元素
- 释放分配的内存空间,包括没有元素的内存
void StrVec::free() { if(elements) //确保不是空指针,就是要有元素 { for(auto p = first_free; p != elements;) //逆序的哦(为啥要逆序我不知道) //可能是为了重用这部分空间,删除好了之后指针指向首元素 { alloc.destroy(--p); } alloc.deallocate(elements, cap-elements); } }
拷贝控制成员
有了前面的工具函数,实现拷贝控制成员很简单:
StrVec::StrVec(const StrVec &s) //拷贝构造函数
{
auto newdata = alloc_n_copy(s.begin(), s.end());
elements = newdata.first;
first_free = newdata.sevond
}
StrVec::~StrVec() {free();} //析构函数
StrVec &StrVec::operator=(const StrVec &rhs)
{
auto data = alloc_n_copy(rhs.begin(), rhs.end());
free();
elements = data.first;
first_free = cap = data.second; //都等于
return *this;
}
reallocate
我们会用到一些之后要学的函数:
void StrVec::reallocate()
{
auto newcapacity = size() ? 2*size() : 1;
//空就分配一个,不空就变为2倍,好好看看,这个写法很装逼哦
auto newdata = alloc.allocate(newcapacity); //分配新内存
auto dest = newdata; //指向新数组的下一个空闲位置
auto elem = elemments; //指向旧数组的下一个元素
for(size_t i=0; i != size(); ++i)
{
alloc.construct(dest++, move(*elem++));
//这里的move函数你就理解为把旧数组元素移动到新数组中,不需要拷贝了
}
free(); //移动好元素就释放旧内存空间
//更新数据成员
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}
写个东西还真是不容易。
#C++工程师#