文章目录
拷贝控制实例
虽然通常来说分配资源的类更需要拷贝控制,但资源管理并不是一个类需要定义自己的拷贝控制成员的唯一原因。一个类也需要拷贝控制的帮助来进行薄记工作。
这里我们将概述两个类的设计,这两个类可能用于邮件应用处理中。两个类名为 Message 和 Folder,分别表示电子邮件(或者其他消息)消息和消息目录。每个 Message 对象可以出现在多个 Folder 中。但是,任意给定的 Message 的内容只有一个副本。这样,如果一条 Message 的内容被改变,则我们从它所在的任何 Folder 来浏览此 Message 时,都会看到改变后的内容。
为了记录 Message 位于哪些 Folder 中,每个 Message 都会保存一个所在 Folder 的指针的 set,同样的,每个 Folder 都保存一个它包含的 Message 的指针的 set。
Message 提供 save 和 remove 操作,来向一个给定 Folder 添加一条 Message 或是从在删除一条 Message。为了创建一个新的 Message,我们会指明消息内容,但不会指出 Folder。为了将一条 Message 放到一个特定的 Folder 中,我们必须调用 save。
当我们拷贝一个 Message 时,副本和原对象是不同的 Message 对象,但两个 Message 都出现在相同的 Folder 中。
当我们销毁一个 Message 时,我们必须从包含此消息的所有 Folder 中删除指向此 Message 的指针。
当我们将一个 Message 对象赋予另一个 Message 对象时,左侧 Message 的内容会被右侧 Message 的内容所替代。我们还必须更新 Folder 集合,从原来包含左侧 Message 的 Folder 中将它删除,并将它添加到包含右侧 Message 的 Folder 中。
Folder 类包含 addMsg 和 remMsg 的成员,分别完成在给定 Folder 对象的消息集合中添加和删除 Message 工作。
Message 类
我们可以编写 Message 类,如下:
class Message {
friend class Folder;
private:
std::set<Folder*> folders; // folders 被隐式初始化为空集合
std::string contents; // 消息文本
void add_to_Folders(const Message&); // 拷贝构造函数, 拷贝赋值运算符, 析构函数均会使用
// 到,将本 Message 添加到指定参数的 Folder 中
void remove_from_Folders(); // 从 folders 中的每个 Folder 中删除本 Message
public:
explicit Message(const std::string& s = ""): contents(s) {}
Message(const Message&);
Message&operator= (const Message&);
void save(Folder&);
void remove(Folder&);
~Message();
};
注意:此函数只有一个构造函数,但其含默认参数,因此它也被当做 Message 的默认构造函数。
save 与 remove 成员
void Message::save(Folder &f) {
folders.insert(&f);
f.addMsg(this);
}
void Message::remove(Folder &f) {
folders.erase(&f);
f.remMsg(this);
}
不仅要更新 Message 所出现在的 Folder,还要在对应 Folder 中更新 Message。
Mes 类的拷贝控制成员
当我们拷贝一个 Message 时,得到的副本应该与原 Message 出现在相同的 Folder 中。所以,我们需要遍历 Folder 指针的 set,对每个指向原 Message 的 Folder 中添加一个指向新的 Message 的指针。拷贝构造函数和拷贝赋值运算符都需要这个工作,因此我们在 private 中定义一个函数来完成这个公共操作:
void Message::add_to_Folders(const Message &m) {
for(auto f : m.folders)
f -> addMsg(this);
}
此例中,我们对 m.folders 中每个 Folder 调用 addMsg。函数 addMsg 会将本 Message 的指针添加到每个 Folder 中。
Message 的拷贝构造函数拷贝给定对象的数据成员:
Message::Message(const Message &m):
contents(m.contents), folders(m.folders) {
add_to_Folders(m);
}
Message 的析构函数
当我们销毁一个 Message 时,我们必须从包含此消息的所有 Folder 中删除指向此 Message 的指针。拷贝赋值运算符也要执行此操作,因此我们会定义一个公共函数来完成工作:
void Message::remove_from_Folders() {
for(auto f : folders)
f -> remMsg(this);
}
函数 remove_from_Folders 的实现类似 add_to_Folders。有了此函数,编写析构函数就很容易了:
Message::~Message() {
remove_from_Folders();
}
剩下的 string, set,编译器会自动调用对应的析构函数。
Message 的拷贝赋值运算符
我们知道,拷贝赋值运算符可能存在自赋值的情况。而这里的 folders 是一个 set,所以我们只能先从左侧运算对象中删除指针,然后再将指针添加到右侧运算对象的 folders 中。
Message& Message::operator=(const Message &rhs) {
remove_from_Folders();
contents = rhs.contents;
folders = rhs.folders;
add_to_Folders(rhs);
return *this;
}
Message 的 swap 函数
通过定义一个 Message 特定版本的 swap,我们可以避免对 contents 和 folders 成员进行不必要的拷贝。
void swap(Message &lhs, Message &rhs){
using std::swap; // 本例中,严格来说不需要,但是这个是一个好习惯
for (auto f : lhs.folders) // 将每个消息的指针从它原来所在的 Folder 中删除
f->remMsg(&lhs);
for (auto f : rhs.folders)
f->remMsg(&rhs);
swap(lhs.contents, rhs.contents); // 使用 swap(set&, set&)
swap(lhs.folders, rhs.folders); // 使用 swap(string&, string&)
for (auto f : lhs.folders) // 将每个 Message 的指针添加到它的新 Folder 中
f->addMsg(&lhs);
for (auto f : rhs.folders)
f->addMsg(&rhs);
}
记一懵点:不明白为什么不调用 remove_from_Folders 与 add_to_Folders
练习 13.37
为 Message 类添加成员,实现向 folders 添加或删除一个给定的 Folder*。这两个成员类似 Folder 的 addMsg 和 remMsg 操作:
void addFolder(Folder* f) { folders.insert(f); }
void remFolder(Folder* f){ folders.erase(f); }
总结
注意:save 是 save,它添加 Folder 的同时,也在对应的 Folder 中添加此 Message。而 addFolder 只添加 Folder,并不在对应的 Folder 中添加 Message。虽然这两个函数的行为很类似,而且好像 save 包括了 addFolder,但是两个函数是不同的需求。可能存在只想添加 Folder 的情况 (如修复 bug 这种时候)。