Team LiB
Previous Section Next Section

13.4. A Message-Handling Example

13.4. 消息处理示例

As an example of a class that needs to control copies in order to do some bookkeeping, we'll sketch out two classes that might be used in a mail-handling application. These classes, Message and Folder, represent, respectively, email (or other) messages and directories in which a message might appear. A given Message might appear in more than one Folder. We'll have save and remove operations on Message that save or remove that message in the specified Folder.

有些类为了做一些工作需要对复制进行控制。为了给出这样的例子,我们将概略定义两个类,这两个类可用于邮件处理应用程序。Message 类和 Folder 类分别表示电子邮件(或其他)消息和消息所出现的目录,一个给定消息可以出现在多个目录中。Message 上有 saveremove 操作,用于在指定 Folder 中保存或删除该消息。

Rather than putting a copy of each Message into each Folder, we'll have each Message hold a set of pointers to the Folders in which this Message appears. Each Folder will also store pointers to the Messages it contains. Figure 13.1 (p. 488) illustrates the data structure we'll implement.

对每个 Message,我们并不是在每个 Folder 中都存放一个副本,而是使每个 Message 保存一个指针集(set),set 中的指针指向该 Message 所在的 Folder。每个 Folder 也保存着一些指针,指向它所包含的 Message。将要实现的数据结构如图 13.1 所示。

Figure 13.1. Message and Folder Class Design
图 13.1. MessageFolder 类设计


When we create a new Message, we will specify the contents of the message but no Folder. Calling save will put a Message in a Folder.

创建新的 Message 时,将指定消息的内容但不指定 Folder。调用 saveMessage 放入一个 Folder

When we copy a Message, we'll copy both the contents of the original message and the set of Folder pointers. We must also add a pointer to this Message to each of the Folders that points to the original Message.

复制一个 Message 对象时,将复制原始消息的内容和 Folder 指针集,还必须给指向源 Message 的每个 Folder 增加一个指向该 Message 的指针。

Assigning one Message to another behaves similarly to copying a Message: After the assignment, the contents and set of Folders will be the same. We'll start by removing the existing left-hand message from the Folders it was in prior to the assignment. Once the old Message is gone, we'll copy the contents and set of Folders from the right-hand operand into the left. We'll also have to add a pointer to the left-hand Message to each Folder in this set.

将一个 Message 对象赋值给另一个,类似于复制一个 Message:赋值之后,内容和 Folder 集将是相同的。首先从左边 Message 在赋值之前所处的 Folder 中删除该 Message。原来的 Message 去掉之后,再将右边操作数的内容和 Folders 集复制到左边,还必须在这个 Folder 集中的每个 Folders 中增加一个指向左边 Message 的指针。

When we destroy a Message, we must update each Folder that points to the Message. Once the Message goes away, those pointers will be no good, so we must remove the pointer to this Message from each Folder in the Message's own set of Folder pointers.

撤销一个 Message 对象时,必须更新指向该 Message 的每个 Folder。一旦去掉了 Message,指向该 Message 的指针将失效,所以必须从该 MessageFolder 指针集的每个 Folder 中删除这个指针。

Looking at this list of operations, we can see that the destructor and the assignment operator share the work of removing messages from the list of Folders that had held a given Message. Similarly, the copy constructor and the assignment operator share the work of adding a Message to a given list of Folders. We'll define a pair of private utility functions to do these tasks.

查看这个操作列表,可以看到,析构函数和赋值操作符分担了从保存给定 MessageFolder 列表中删除消息的工作。类似地,复制构造函数和赋值操作符分担将一个 Message 加到给定 Folder 列表的工作。我们将定义一对 private 实用函数完成这些任务。

The Message Class

Message

Given this design, we can write a fair bit of our Message class:

对于以上的设计,可以如下编写 Message 类的部分代码:

     class Message {
     public:
         // folders is initialized to the empty set automatically
         Message(const std::string &str = ""):
                       contents (str) { }
         // copy control: we must manage pointers to this Message
         // from the Folders pointed to by folders
         Message(const Message&);
         Message& operator=(const Message&);
         ~Message();
         // add/remove this Message from specified Folder's set of messages
         void save (Folder&);
         void remove(Folder&);
     private:
         std::string contents;      // actual message text
         std::set<Folder*> folders; // Folders that have this Message
         // Utility functions used by copy constructor, assignment, and destructor:
         // Add this Message to the Folders that point to the parameter
         void put_Msg_in_Folders(const std::set<Folder*>&);
         // remove this Message from every Folder in folders
         void remove_Msg_from_Folders();
     };

The class defines two data members: contents, which is a string that holds the actual message, and folders, which is a set of pointers to the Folders in which this Message appears.

Message 类定义了两个数据成员:contents 是一个保存实际消息的 stringfolders 是一个 set,包含指向该 Message 所在的 Folder 的指针。

The constructor takes a single string parameter representing the contents of the message. The constructor stores a copy of the message in contents and (implicitly) initializes the set of Folders to the empty set. This constructor provides a default argument, which is the empty string, so it also serves as the Message default constructor.

构造函数接受单个 string 形参,表示消息的内容。构造函数将消息的副本保存在 contents 中,并(隐式)将 Folderset 初始化为空集。这个构造函数提供一个默认实参(为空串),所以它也可以作为默认构造函数。

The utility functions provide the actions shared among the copy-control members. The put_Msg_in_Folders function adds a copy of its own Message to the Folders that point to the given Message. After this function completes, each Folder that points to the parameter will also point to this Message. This function will be used by both the copy constructor and the assignment operator.

实用函数提供由复制控制成员共享的行为。put_Msg_in_Folders 函数将自身 Message 的一个副本添加到指向给定 Message 的各 Folder 中,这个函数执行完后,形参指向的每个 Folder 也将指向这个 Message。复制构造函数和赋值操作符都将使用这个函数。

The remove_Msg_from_Folders function is used by the assignment operator and destructor. It removes the pointer to this Message from each of the Folders in the folders member.

remove_Msg_from_Folders 函数用于赋值操作符和析构函数,它从 folders 成员的每个 Folder 中删除指向这个 Message 的指针。

Copy Control for the Message Class

Message 类的复制控制

When we copy a Message, we have to add the newly created Message to each Folder that holds the Message from which we're copying. This work is beyond what the synthesized constructor would do for us, so we must define our own copy constructor:

复制 Message 时,必须将新创建的 Message 添加到保存原 Message 的每个 Folder 中。这个工作超出了合成构造函数的能力范围,所以我们必须定义自己的复制构造函数:

     Message::Message(const Message &m):
         contents(m.contents), folders(m.folders)
     {
         // add this Message to each Folder that points to m
         put_Msg_in_Folders(folders);
     }

The copy constructor initializes the data members of the new object as copies of the members from the old. In addition to these initializationswhich the synthesized copy constructor would have done for uswe must also iterate through folders, adding this Message to each Folder in that set. The copy constructor uses the put_Msg_in_Folder function to do this work.

复制构造函数将用旧对象成员的副本初始化新对象的数据成员。除了这些初始化之外(合成复制构造函数可以完成这些初始化),还必须用 folders 进行迭代,将这个新的 Message 加到那个集的每个 Folder 中。复制构造函数使用 put_Msg_in_Folder 函数完成这个工作。

When we write our own copy constructor, we must explicitly copy any members that we want copied. An explicitly defined copy constructor copies nothing automatically.

编写自己的复制构造函数时,必须显式复制需要复制的任意成员。显式定义的复制构造函数不会进行任何自动复制。



As with any other constructor, if we do not initialize a class member, then that member is initialized using the member's default constructor. Default initialization in a copy constructor does not use the member's copy constructor.

像其他任何构造函数一样,如果没有初始化某个类成员,则那个成员用该成员的默认构造函数初始化。复制构造函数中的默认初始化不会使用成员的复制构造函数。

The put_Msg_in_Folders Member

put_Msg_in_Folders 成员

put_Msg_in_Folders iterates through the pointers in the folders member of the parameter rhs. These pointers denote each Folder that points to rhs. We need to add a pointer to this Message to each of those Folders.

put_Msg_in_Folders 通过形参 rhs 的成员 folders 中的指针进行迭代。这些指针表示指向 rhs 的每个 Folder,需要将指向这个 Message 的指针加到每个 Folder

The function does this work by looping through rhs.folders, calling the Folder member named addMsg. That function will do whatever it takes to add a pointer to this Message to that Folder:

函数通过 rhs.folders 进行循环,调用命名为 addMsgFolder 成员来完成这个工作,addMsg 函数将指向该 Message 的指针加到 Folder 中。

     // add this Message to Folders that point to rhs
     void Message::put_Msg_in_Folders(const set<Folder*> &rhs)
     {
         for(std::set<Folder*>::const_iterator beg = rhs.begin();
                                          beg != rhs.end(); ++beg)
             (*beg)->addMsg(this);     // *beg points to a Folder
     }

The only tricky part in this function is the call to addMsg:

这个函数中唯一复杂的部分是对 addMsg 的调用:

     (*beg)->addMsg(this); // *beg points to a Folder

That call starts with (*beg), which dereferences the iterator. Dereferencing the iterator yields a pointer to a Folder. The expression then applies the arrow operator to the Folder pointer in order to run the addMsg operation. We pass this, which points to the Message we want to add to the Folder.

那个调用以 (*beg) 开关,它解除迭代器引用。解除迭代器引用将获得一个指向 Folder 的指针。然后表达式对 Folder 指针应用箭头操作符以执行 addMsg 操作,将 this 传给 addMsg,该指针指向我们想要添加到 Folder 中的 Message

Message Assignment Operator

Message 赋值操作符

Assignment is more complicated than the copy constructor. Like the copy constructor, assignment must assign the contents and update folders to match that of the right-hand operand. It must also add this Message to each of the Folders that points to the rhs. We can use our put_Msg_in_Folders function to do this part of the assignment.

赋值比复制构造函数更复杂。像复制构造函数一样,赋值必须对 contents 赋值并更新 folders 使之与右操作数的 folders 相匹配。它还必须将该 Message 加到指向 rhs 的每个 Folder 中,可以使用 put_Msg_in_Folders 函数完成赋值的这一部分工作。

Before copying from the rhs, we must first remove this Message from each of the Folders that currently point to it. We'll need to iterate through folders, removing the pointer to this Message from each Folder in folders. The function named remove_Msg_from_Folders will do this work.

在从 rhs 复制之前,必须首先从当前指向该 Message 的每个 Folder 中删除它。我们需要通过 folders 进行迭代,从 folders 的每个 Folder 中删除指向该 Message 的指针。命名为 remove_Msg_from_Folders 的函数将完成这项工作。

Given remove_Msg_from_Folders and put_Msg_in_Folders, which do the real work, the assignment operator itself is fairly simple:

对于完成实际工作的 remove_Msg_from_Foldersput_Msg_in_Folders,赋值操作符本身相当简单:

     Message& Message::operator=(const Message &rhs)
     {
         if (&rhs != this) {
             remove_Msg_from_Folders(); // update existing Folders
             contents = rhs.contents;   // copy contents from rhs
             folders = rhs.folders;     // copy Folder pointers from rhs
             // add this Message to each Folder in rhs
             put_Msg_in_Folders(rhs.folders);
         }
         return *this;
     }

The assignment operator starts by checking that the left- and right-hand operands are not the same. We do this check for reasons that will become apparent as we walk through the rest of the function. Assuming that the operands are different objects, we call remove_Msg_from_Folders to remove this Message from each of the Folders in the folders member. Once that work is done, we have to assign the contents and folders members from the right-hand operand to this object. Finally, we call put_Msg_in_Folders to add a pointer to this Message in each of the Folders that also point to rhs.

赋值操作符首先检查左右操作数是否相同。查看函数的后续部分可以清楚地看到进行这一检查的原因。假定操作数是不同对象,调用 remove_Msg_from_Foldersfolders 成员的每个 Folder 中删除该 Message。一旦这项工作完成,必须将右操作数的 contentsfolders 成员赋值给这个对象。最后,调用 put_Msg_in_Folders 将指向这个 Message 的指针添加至指向 rhs 的每个 Folder 中。

Now that we've seen work that remove_Msg_from_Folders does, we can see why we start the assignment operator by checking that the objects are different. Assignment involves obliterating the left-hand operand. Once the members of the left-hand operand are destroyed, those in the right-hand operand are assigned to the corresponding left-hand members. If the objects were the same, then destroying the left-hand members would also destroy the right-hand members!

了解了 remove_Msg_from_Folders 的工作之后,我们来看看为什么赋值操作符首先要检查对象是否不同。赋值时需删除左操作数,并在撤销左操作数的成员之后,将右操作数的成员赋值给左操作数的相应成员。如果对象是相同的,则撤销左操作数的成员也将撤销右操作数的成员!

It is crucially important for assignment operators to work correctly, even when an object is assigned to iself. A common way to ensure this behavior is by checking explicitly for self-assignment.

即使对象赋值给自己,赋值操作符的正确工作也非常重要。保证这个行为的通用方法是显式检查对自身的赋值。



The remove_Msg_from_Folders Member

remove_Msg_from_Folders 成员

The implementation of the remove_Msg_from_Folders function is similar to that of put_Msg_in_Folders, except that this time we'll call remMsg to remove this Message from each Folder pointed to by folders:

除了调用 remMsgfolders 指向的每个 Folder 中删除这个 Message 之外,remove_Msg_from_Folders 函数的实现与 put_Msg_in_Folders 类似:

     // remove this Message from corresponding Folders
     void Message::remove_Msg_from_Folders()
     {
         // remove this message from corresponding folders
         for(std::set<Folder*>::const_iterator beg =
               folders.begin (); beg != folders.end (); ++beg)
            (*beg)->remMsg(this); // *beg points to a Folder
     }

The Message Destructor

Message 析构函数

The remaining copy-control function that we must implement is the destructor:

剩下必须实现的复制控制函数是析构函数:

     Message::~Message()
     {
         remove_Msg_from_Folders();
     }

Given the remove_Msg_from_Folders function, writing the destructor is trivial. We call that function to clean up folders. The system automatically invokes the string destructor to free contents and the set destructor to clean up the memory used to hold the folders member. Thus, the only work for the Message destructor is to call remove_Msg_from_Folders.

有了 remove_Msg_from_Folders 函数,编写析构函数将非常简单。我们调用 remove_Msg_from_Folders 函数清除 folders,系统自动调用 string 析构函数释放 contents,自动调用 set 析构函数清除用于保存 folders 成员的内存,因此,Message 析构函数唯一要做的是调用 remove_Msg_from_Folders

The assignment operator often does the same work as is needed in the copy constructor and destructor. In such cases, the common work should be put in private utility functions.

赋值操作符通常要做复制构造函数和析构函数也要完成的工作。在这种情况下,通用工作应在 private 实用函数中。



Exercises Section 13.4

Exercise 13.16:

Write the Message class as described in this section.

编写本节中描述的 Message 类。

Exercise 13.17:

Add functions to the Message class that are analogous to the Folder operations addMsg and remMsg. These functions, which could be named addFldr and remFldr, should take a pointer to a Folder and insert that pointer into folders. These functions can be private because they will be used only in the implementation of the Folder class.

Message 类增加与 FolderaddMsgremMsg 操作类似的函数。这些函数可以命名为 addFldrremFldr,应接受一个指向 Folder 的指针并将该指针插入到 folders。这些函数可为 private 的,因为它们将仅在 Message 类的实现中使用。

Exercise 13.18:

Write the corresponding Folder class. That class should hold a set<Message*> that contains elements that point to Messages.

编写相应的 Folder 类。该类应保存一个 set<Message*>,包含指向 Message 的元素。

Exercise 13.19:

Add a save and remove operation to the Message and Folder classes. These operations should take a Folder and add (or remove) that Folder to (from) the set of Folders that point to this Message. The operation must also update the Folder to know that it points to this Message, which can be done by calling addMsg or remMsg.

Message 类和 Folder 类中增加 saveremove 操作。这些操作应接受一个 Folder,并将该 Folder 加入到指向这个 MessageFolder 集中(或从其中删除 Folder)。操作还必须更新 Folder 以反映它指向该 Message,这可以通过调用 addMsgremMsg


Team LiB
Previous Section Next Section