C++基础的不能再基础的学习笔记——拷贝控制示例(二)
2018-02-27 20:47
651 查看
拷贝控制示例
我们在之前的博客上了解到了,需要管理资源的类通常要定义拷贝控制操作、swap操作。http://blog.csdn.net/fancynece/article/details/79346314但是,①资源管理并不是一个类需要定义自己的拷贝控制成员的唯一原因。
一些类需要拷贝控制成员的帮助来进行②簿记工作或其他操作。
我们通过一个例子来说明,簿记工作需要拷贝控制成员。
定义两个类Message和Folder,分别表示电子邮件消息和消息目录。
一个Message可以出现在多个Folder中
每个Message只有一个副本
我们在每个Message中,用set记录下包含它的Folder的指针;在每个Folder中,用set记录下包含的所有Message的指针。
Message提供save和remove操作,用来将对象保存到Folder中或从Folder删除。
当我们拷贝一个Message时,需要拷贝set,并且在所有包含它的Folder的set中添加该Message的指针
当我们销毁一个Message时,需要在所有包含它的Folder的set中删除该Message的指针
当我们进行赋值操作时,左侧Message的内容被销毁,右侧Message的内容拷贝到左侧。
Folder类提供addMsg和remMsg操作,用来添加或删除Message对象
class Message { friend class Folder; friend void swap(Message &lm, Message &rm); public: explicit Message(const string &str = "") :contents(str) {} Message(const Message&); Message& operator=(const Message&); ~Message(); void save(Folder &f) { folders.insert(&f); f.addMsg(this); } void remove(Folder &f) { folders.erase(&f); f.remMsg(this); } private: string contents; //内容 set<Folder*> folders; void add_to_Folders(const Message&); void remove_from_Folders(); }; Message::Message(const Message &m):contents(m.contents),folders(m.folders) { add_to_Folders(m); } Message & Message::operator=(const Message &m) { remove_from_Folders(); contents = m.contents; folders = m.folders; add_to_Folders(m); return *this; } Message::~Message() { remove_from_Folders(); } void Message::add_to_Folders(const Message &m) { for (auto c : m.folders) c->addMsg(this); } void Message::remove_from_Folders() { for (auto c : folders) c->remMsg(this); } void swap(Message & lm, Message & rm) { using std::swap; for (auto c : lm.folders) c->remMsg(&lm); for (auto c : rm.folders) c->remMsg(&rm); swap(lm.contents, rm.contents); swap(lm.folders, rm.folders); for (auto c : lm.folders) c->addMsg(&lm); for (auto c : rm.folders) c->addMsg(&rm); }
动态内存管理类
某些类需要在运行时分配可变大小的内存空间。这种类通常可以一般也都会使用标准库容器来保存它们的数据。但是某些类需要自己进行内存分配,这些类一般来说必须定义自己的拷贝控制成员来管理所分配的内存。
我们将实现一个标准库vector类的简化版本StrVec,不适用模板,并且只用于string。
StrVec类的设计
我们已经知道,vector类将元素保存在连续内存中,添加元素时,若有空间则加入,若没有空间则重新分配内存,并将原有元素移动到新内存中,释放旧空间并添加新元素。因此我们的StrVec类也要如此。
我们将使用一个allocator(一个类模板,将内存分配和对象构造分离开来)来获得原始内存。添加对象时,由于allocator分配的内存是未构造的,我们将用allocator的construct成员在原始内存中创建对象。删除对象时,使用destroy成员来销毁元素。
StrVec有三个指针成员指向其元素所使用的内存,有一个静态成员来分配内存。
elements : 指向分配的内存中的首元素
first_free : 指向最后一个实际元素之后的位置
cap : 指向分配的内存末尾之后的位置
alloc : 分配内存的静态成员,allocator < string >类型
StrVec还有4个工具函数。
alloc_n_copy : 分配内存,拷贝指定范围中的元素
free : 销毁构造的元素并释放内存
chk_n_alloc : 保证StrVec至少有容纳一个新元素的空间
reallocate : 在内存用完时为StrVec分配新内存
class StrVec { public: StrVec() :elements(nullptr), first_free(nullptr), cap(nullptr) {} StrVec(const StrVec&); StrVec& operator = (const StrVec &); string& operator[](const size_t); ~StrVec(); //插入 void push_back(const string&); string* insert(string*,const string&); string* insert(string*,const string*,const string*); //删除 void pop_back(); string* erase(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: allocator<string> alloc; //分配内存 string *elements; string *first_free; string *cap; void chk_n_alloc() { if (size() == capacity()) reallocate(); } pair<string*, string*> alloc_n_copy(const string *, const string *); void free(); void reallocate(); }; StrVec::StrVec(const StrVec &s) { pair<string*,string*> p; p = alloc_n_copy(s.begin(), s.end()); elements = p.first; first_free = p.second; cap = p.second; } StrVec & StrVec::operator=(const StrVec &s) { auto p = alloc_n_copy(s.begin(), s.end()); free(); elements = p.first; first_free = p.second; cap = p.second; return *this; } string& StrVec::operator[](const size_t loc) { return *(elements + loc); } StrVec::~StrVec() { free(); cout << "析构函数被调用辣" << endl; } void StrVec::push_back(const string &s) { //添加元素到容器末尾 //确保有位置 chk_n_alloc(); //加入元素 alloc.construct(first_free++,s); } string* StrVec::insert(string *p, const string &s) { assert(p >= begin() && p <= end()); size_t loc = p - elements; chk_n_alloc(); auto e = elements + loc; alloc.construct(end()); for (auto b = end(); b > e; --b) *b = *(b - 1); *e = s; first_free++; cout << "插入了一个元素" << endl; return e; } string * StrVec::insert(string *, const string *, const string *) { return nullptr; } void StrVec::pop_back() { assert(elements != first_free); alloc.destroy(--first_free); } string * StrVec::erase(string *p) { assert(p >= begin() && p < end()); for (auto b = p; b < end() - 1; ++b) *b = *(b + 1); alloc.destroy(end() - 1); return ++p; } pair<string*, string*> StrVec::alloc_n_copy(const string *b, const string *e) { //分配空间保存给定范围中的元素,返回首元素指针和尾元素下一位置的指针 //分配空间 auto data = alloc.allocate(e - b); //初始化并返回一个pair,将[b,e)赋值给迭代器data指定的未构造的内存中 return {data,uninitialized_copy(b,e,data)}; } void StrVec::free() { if (elements) { //销毁容器中的所有元素 for (auto p = first_free; p != elements;) alloc.destroy(--p);//析构string //释放内存空间 alloc.deallocate(elements, cap - elements); } } void StrVec::reallocate() { //分配新内存空间 auto newcapacity = size() ? 2 * size() : 1; auto newdata = alloc.allocate(newcapacity); //移动数据 auto dest = newdata; auto elem = elements; for (size_t i = 0; i != size(); ++i) alloc.construct(dest++, std::move(*elem++)); //移动而不是拷贝 //释放旧空间 free(); elements = newdata; first_free = dest; cap = elements + newcapacity; cout << "重新分配内存辣 当前内存为" << cap - elements << "元素个数为" << first_free - elements << endl; }
现在,我们只有一个函数reallocate没有实现。这个函数需要完成如下功能。
为容器分配一个更大的内存
在内存的前一部分构造对象,保存现有元素
销毁原内存空间中的元素,释放内存
我们知道,拷贝一个string就必须拷贝数据,它就会有两个用户。但是,如果是reallocate拷贝StrVec中的string,则在拷贝之后,只有一个用户(旧内存被释放了)。
因此,拷贝string是多余的,分配和释放string会造成很多的额外开销,所以我们想要移动它们。
移动构造函数和std::move
通过使用新标准库引入的两种机制,我们就可以避免string库的拷贝。移动构造函数:移动构造函数通常将对象移动而不是拷贝到正在创建的对象中。对于string,我们可以想象每个string都有一个指向char数组的指针,可以假定string的移动构造函数进行了指针的拷贝。
sd::move : 定义在utility头文件中。① 当我们希望用string的移动构造函数时,必须调用move来表示这个希望 ② 我们使用move时,直接调用std::move而不是move。
void StrVec::reallocate() { //分配新内存空间 auto newcapacity = size() ? 2 * size() : 1; auto newdata = alloc.allocate(newcapacity); //移动数据 auto dest = newdata; auto elem = elements; for (size_t i = 0; i != size(); ++i) alloc.construct(dest++, std::move(*elem++)); //移动而不是拷贝 //释放旧空间 free(); elements = newdata; first_free = dest; cap = elements + newcapacity; }
相关文章推荐
- C++基础的不能再基础的学习笔记——拷贝控制(一)
- C++基础的不能再基础的学习笔记——类(一)
- C++基础的不能再基础的学习笔记——顺序容器(基本操作)
- C++基础的不能再基础的学习笔记——类型转换
- C++基础的不能再基础的学习笔记——标准库类型string
- C++基础的不能再基础的学习笔记——关联容器
- C++基础的不能再基础的学习笔记——标准库类型vector(基础)
- C++学习笔记【第三部分第十三章:拷贝控制】
- C++基础的不能再基础的学习笔记——面向对象程序设计(一)
- C++基础的不能再基础的学习笔记——const限定符
- C++基础的不能再基础的学习笔记——类(二)
- C++基础的不能再基础的学习笔记——复合类型
- C++基础的不能再基础的学习笔记——迭代器(基础)
- C++基础的不能再基础的学习笔记——顺序容器(其他操作)
- c++学习笔记--拷贝控制的三/五法则
- C++基础的不能再基础的学习笔记——面向对象程序设计(二)
- C++学习笔记4-----类的继承基础概念
- C++基础(学习笔记)
- C++基础知识学习笔记(二)
- C++学习笔记(一)--基础知识sizeof用法