您的位置:首页 > 编程语言 > C语言/C++

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: