C++ Primer 学习笔记_56_类与数据抽象 --消息处理示例
复制控制
--消息处理示例
说明:
有些类为了做一些工作需要对复制进行控制。为了给出这样的例子,我们将概略定义两个类,这两个类可用于邮件处理应用程序。Message类和 Folder类分别表示电子邮件(或其他)消息和消息所出现的目录,一个给定消息可以出现在多个目录中。Message上有 save和 remove操作,用于在指定Folder中保存或删除该消息。
数据结构:
对每个Message,我们并不是在每个Folder中都存放一个副本,而是使每个Message保存一个指针集(set),set中的指针指向该Message所在的Folder。每个Folder也保存着一些指针,指向它所包含的Message。数据结构如图所示。
操作:
创建新的Message时,将指定消息的内容但不指定Folder。调用save将 Message放入一个Folder。
复制一个Message对象时,将复制原始消息的内容和Folder指针集,还必须给指向源 Message的每个Folder增加一个指向该Message的指针。
将一个Message对象赋值给另一个,类似于复制一个Message:赋值之后,内容和 Folder集将是相同的。首先从左边Message在赋值之前所处的Folder中删除该Message。原来的Message去掉之后,再将右边操作数的内容和Folders集复制到左边,还必须在这个Folder集中的每个Folders中增加一个指向左边Message的指针。
撤销一个Message对象时,必须更新指向该Message的每个 Folder。一旦去掉了 Message,指向该Message的指针将失效,所以必须从该Message的Folder指针集的每个Folder中删除这个指针。
可以看到,析构函数和赋值操作符分担了从保存给定Message的 Folder列表中删除消息的工作。类似地,复制构造函数和赋值操作符分担将一个Message加到给定Folder列表的工作。我们将定义一对private实用函数完成这些任务。
实现:
1、Message类
class Message { public: Message(const std::string &str = ""):contents(str){}; Message(const Message &); Message &operator=(const Message &); ~Message(); void save(Folder &); void remove(Folder &); private: std::string contents; std::set<Folder *> folders; void put_Msg_in_Folders(const std::set<Folder *> &); void remove_Msg_from_Folders(); };
put_Msg_in_Folders函数将自身Message的一个副本添加到指向给定Message的各Folder中,这个函数执行完后,形参指向的每个Folder也将指向这个Message。复制构造函数和赋值操作符都将使用这个函数。
remove_Msg_from_Folders函数用于赋值操作符和析构函数,它从folders成员的每个Folder中删除指向这个Message的指针。
2、Message类的复制控制
复制Message时,必须将新创建的Message添加到保存原Message的每 个 Folder中。这个工作超出了合成构造函数的能力范围,所以我们必须定义自己的复制构造函数:
Message::Message(const Message &m): contents(m.contents),folders(m.folders) { put_Msg_in_Folders(folders); }
复制构造函数将用旧对象成员的副本初始化新对象的数据成员。除了这些初始化之外(合成复制构造函数可以完成这些初始化),还必须用folders进行迭代,将这个新的Message加到那个集的每个Folder中。复制构造函数使用put_Msg_in_Folder函数完成这个工作。
编写自己的复制构造函数时,必须显式复制需要复制的任意成员。显式定义的复制构造函数不会进行任何自动复制。
像其他任何构造函数一样,如果没有初始化某个类成员,则那个成员用该成员的默认构造函数初始化。复制构造函数中的默认初始化不会使用成员的复制构造函数。
3、put_Msg_in_Folder成员
put_Msg_in_Folders通过形参 rhs的成员 folders中的指针进行迭代。这些指针表示指向rhs的每个Folder,需要将指向这个Message的指针加到每个Folder。
函数通过rhs.folders进行循环,调用命名为addMsg的 Folder成员来完成这个工作,addMsg函数将指向该Message的指针加到Folder中。
void Message::put_Msg_in_Folders(const set<Folder *> &rhs) { for (set<Folder *>::const_iterator beg = rhs.begin(); beg != rhs.end(); ++beg) { /* *(*beg)解除迭代器引用。解除迭代器引用将获得一个指向 Folder 的指针 *然后表达式对 Folder 指针应用箭头操作符以执行addMsg 操作 *将 this 传给 addMsg,该指针指向我们想要添加到 Folder 中的Message */ (*beg) -> addMsg(this); } }
4、Message赋值操作符
赋值比复制构造函数更复杂。像复制构造函数一样,赋值必须对contents赋值并更新folders使之与右操作数的folders相匹配。它还必须将该Message加到指向rhs的每个 Folder中,可以使用put_Msg_in_Folders函数完成赋值的这一部分工作【但是需要注意的是函数的实参此时由folders换成了rhs.folders了】。
在从rhs复制之前,必须首先从当前指向该Message的每个Folder中删除它。我们需要通过folders进行迭代,从folders的每个Folder中删除指向该Message的指针。命名为remove_Msg_from_Folders的函数将完成这项工作。
对于完成实际工作的remove_Msg_from_Folders和put_Msg_in_Folders,赋值操作符本身相当简单:
Message &Message::operator=(const Message &rhs) { if (&rhs != this) { remove_Msg_from_Folders(); contents = rhs.contents; folders = rhs.folders; put_Msg_in_Folders(rhs.folders); } return *this; }
假定操作数是不同对象,调用remove_Msg_from_Folders从 folders成员的每个Folder中删除该Message。一旦这项工作完成,必须将右操作数的contents和 folders成员赋值给这个对象。最后,调用put_Msg_in_Folders将指向这个Message的指针 添加至指向rhs的每个 Folder中。
了解了remove_Msg_from_Folders的工作之后,我们来看看为什么赋值操作符首先要检查对象是否不同。赋值时需删除左操作数,并在撤销左操作数的成员之后,将右操作数的成员赋值给左操作数的相应成员。如果对象是相同的,则撤销左操作数的成员也将撤销右操作数的成员!
即使对象赋值给自己,赋值操作符的正确工作也非常重要。保证这个行为的通用方法是显式检查对自身的赋值。
5、remove_Msg_from_Folders成员
void Message::remove_Msg_from_Folders() { for (set<Folder *>::iterator beg = folders.begin(); beg != folders.end(); ++beg) { (*beg) -> remMsg(this); } }
6、Message析构函数
Message::~Message() { remove_Msg_from_Folders(); }
有了remove_Msg_from_Folders函数,编写析构函数将非常简单。我们调用remove_Msg_from_Folders函数清除folders,系统自动调用string析构函数释放contents,自动调用set析构函数清除用于保存folders成员的内存,因此,Message析构函数唯一要做的是调用remove_Msg_from_Folders。
【最佳实践:】
赋值操作符通常需要做复制构造函数函数和析构函数也要完成的工作。在这种情况下,通用工作应该放在private实用函数中。
//P419 习题13.17 //under the private labor void addFldr(Folder *f) { folders.insert(f); } void remFldr(Folder *f) { folders.erase(f); }
//习题13.19 void Message::save(Folder &f) { folders.insert(&f); f.addMsg(this); } void Message::remove(Folder &f) { folders.erase(&f); f.remMsg(this); }
//拓展:完成完整的类Folder和Message,完成习题13.16~13.19内容 //in Folder.h #ifndef FOLDER_H_INCLUDED #define FOLDER_H_INCLUDED #include <set> #include <string> class Message; class Folder { public: Folder() {} ~Folder(); void addMsg(Message *); void remMsg(Message *); private: std::set<Message *> messages; void remove_Fldr_form_Messages(); }; class Message { public: Message(const std::string &str = ""):contents(str) {}; Message(const Message &); Message &operator=(const Message &); ~Message(); void save(Folder &); void remove(Folder &); void addFldr(Folder *); void remFldr(Folder *); private: std::string contents; std::set<Folder *> folders; void put_Msg_in_Folders(const std::set<Folder *> &); void remove_Msg_from_Folders(); }; #endif // FOLDER_H_INCLUDED
//in Folder.cpp #include "Folder.h" #include <set> using namespace std; Folder::~Folder() { remove_Fldr_form_Messages(); } void Folder::addMsg(Message *rhs) { messages.insert(rhs); } void Folder::remMsg(Message *rhs) { messages.erase(rhs); } void Folder::remove_Fldr_form_Messages() { for (std::set<Message *>::const_iterator beg = messages.begin(); beg != messages.end(); ++beg) { (*beg) -> remFldr(this); } } Message::Message(const Message &m):contents(m.contents),folders(m.folders) { put_Msg_in_Folders(folders); } Message &Message::operator=(const Message &rhs) { if (&rhs != this) { remove_Msg_from_Folders(); contents = rhs.contents; folders = rhs.folders; put_Msg_in_Folders(rhs.folders); } return *this; } Message::~Message() { remove_Msg_from_Folders(); } void Message::put_Msg_in_Folders(const set<Folder *> &rhs) { for (set<Folder *>::const_iterator beg = rhs.begin(); beg != rhs.end(); ++beg) { (*beg) -> addMsg(this); } } void Message::remove_Msg_from_Folders() { for (set<Folder *>::iterator beg = folders.begin(); beg != folders.end(); ++beg) { (*beg) -> remMsg(this); } } void Message::save(Folder &f) { folders.insert(&f); //更新Message所属的目录 f.addMsg(this); //更新目录 } void Message::remove(Folder &f) { folders.erase(&f); //更新Message所属的目录 f.remMsg(this); //更新目录 } void Message::addFldr(Folder *f) { folders.insert(f); } void Message::remFldr(Folder *f) { folders.erase(f); }