Quoted C++ Primer 5th Chapter 13.5 Classes That Management Dynamic Memory(P.519)
Introduce:
As an example of a class that needs copy control 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 kinds of)messages, and directories in which a message might appear. Each Message can appear in multiple Folders. However, there will be only one copy of the contents of any given Message. That way, if the contents of a Message are changed, those changes will appear when we view that Message from any of its Folders
Design:
To keep track of which Messages are in which Folders, each Message will store a set of pointers to the Folders in which it appears, and each Folder will contain a set of pointers to its Messages. Figure 13.1 illustrates this design
Our Message class will provide save andremove operations to add or remove a Message from a specified Folder. To create a new Message, we will specify the contents of the message but no Folder. To put a Message in a particular Folder,we must call save.
When we copy a Message, the copy and the original will be distinct Messages, but both Messages should appear in the same set of Folders. Thus, copying a Message will copy the contents and the set of Folder pointers. It must also add a pointer to the newly created Message to each of those Folders.
When we destroy a Message, that Message no longer exists. Therefore, destroying a Message must remove pointers to that Message from the Folders that had contained that Message.
When we assign one Message to another, we’ll replace the contents of the left-hand Message with those in the right-hand side. We must also update the set of Folders, removing the left-hand Message from its previous Folders and adding that Message to the Folders in which the right-hand Message appears.
Looking at this list of operations, we can see that both the destructor and the copy-assignment operator have to remove this Message from the Folders that point to it.Similarly, both the copy constructor and the copy-assignment operator add a Message to a given list of Folders. We’ll define a pair of private utility functions
to do these tasks
The Folder class will need analogous copy control members to add or remove itself from the Messages it stores.
Best Practices:The copy-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
The Message and Folder Class
code quoted as follow:
https://github.com/Mooophy/Cpp-Primer/blob/master/ch13/README.md
#include <string>
#include <set>
class Folder;
class Message {
friend void swap(Message &, Message &);
friend class Folder;
public:
explicit Message(const std::string &str = ""):contents(str) { }
Message(const Message&);
Message& operator=(const Message&);
~Message();
void save(Folder&);
void remove(Folder&);
void print_debug();
private:
std::string contents;
std::set<Folder*> folders;
void add_to_Folders(const Message&);
void remove_from_Folders();
void addFldr(Folder *f) { folders.insert(f); }
void remFldr(Folder *f) { folders.erase(f); }
};
void swap(Message&, Message&);
class Folder {
friend void swap(Folder &, Folder &);
friend class Message;
public:
Folder() = default;
Folder(const Folder &);
Folder& operator=(const Folder &);
~Folder();
void print_debug();
private:
std::set<Message*> msgs;
void add_to_Message(const Folder&);
void remove_from_Message();
void addMsg(Message *m) { msgs.insert(m); }
void remMsg(Message *m) { msgs.erase(m); }
};
void swap(Folder &, Folder &);
#include "ex13_34_36_37.h"
#include <iostream>
void swap(Message &lhs, Message &rhs)
{
using std::swap;
lhs.remove_from_Folders(); // Use existing member function to avoid duplicate code.
rhs.remove_from_Folders(); // Use existing member function to avoid duplicate code.
swap(lhs.folders, rhs.folders);
swap(lhs.contents, rhs.contents);
lhs.add_to_Folders(lhs); // Use existing member function to avoid duplicate code.
rhs.add_to_Folders(rhs); // Use existing member function to avoid duplicate code.
}
// Message Implementation
void Message::save(Folder &f)
{
addFldr(&f); // Use existing member function to avoid duplicate code.
f.addMsg(this);
}
void Message::remove(Folder &f)
{
remFldr(&f); // Use existing member function to avoid duplicate code.
f.remMsg(this);
}
void Message::add_to_Folders(const Message &m)
{
for (auto f : m.folders)
f->addMsg(this);
}
Message::Message(const Message &m)
: contents(m.contents), folders(m.folders)
{
add_to_Folders(m);
}
void Message::remove_from_Folders()
{
for (auto f : folders)
f->remMsg(this);
}
Message::~Message()
{
remove_from_Folders();
}
Message &Message::operator=(const Message &rhs)
{
remove_from_Folders();
contents = rhs.contents;
folders = rhs.folders;
add_to_Folders(rhs);
return *this;
}
void Message::print_debug()
{
std::cout << contents << std::endl;
}
// Folder Implementation
void swap(Folder &lhs, Folder &rhs)
{
using std::swap;
lhs.remove_from_Message();
rhs.remove_from_Message();
swap(lhs.msgs, rhs.msgs);
lhs.add_to_Message(lhs);
rhs.add_to_Message(rhs);
}
void Folder::add_to_Message(const Folder &f)
{
for (auto m : f.msgs)
m->addFldr(this);
}
Folder::Folder(const Folder &f)
: msgs(f.msgs)
{
add_to_Message(f);
}
void Folder::remove_from_Message()
{
for (auto m : msgs)
m->remFldr(this);
}
Folder::~Folder()
{
remove_from_Message();
}
Folder &Folder::operator=(const Folder &rhs)
{
remove_from_Message();
msgs = rhs.msgs;
add_to_Message(rhs);
return *this;
}
void Folder::print_debug()
{
for (auto m : msgs)
std::cout << m->contents << " ";
std::cout << std::endl;
}
We did not use copy and swap to define the Message assignment operator. Why do you suppose this is so?
@Mooophy The copy and swap is an elegant way when working with dynamicly allocated memory. In the Message class , nothing is allocated dynamically. Thus using this idiom makes no sense and will make it more complicated to implement due to the pointers that point back.
@pezy In this case, swap
function is special. It will be clear two Message
's folders , then swap members, and added themselves to each folders. But, Message
assignment operator just clear itself, and copy the members, and added itself to each folders. The rhs
don't need to clear and add to folders. So, if using copy and swap to define, it will be very inefficiency.
Add a move constructor and move-assignment operator
#include <string>
#include <set>
#ifndef _MSC_VER
#define NOEXCEPT noexcept
#else
#define NOEXCEPT
#endif
class Folder;
class Message
{
friend void swap(Message &, Message &);
friend void swap(Folder &, Folder &);
friend class Folder;
public:
explicit Message(const std::string &str = "") : contents(str) { }
Message(const Message&);
Message& operator=(const Message&);
Message(Message &&m); // need to update the Folders
Message& operator=(Message&&); // need to update the Folders
~Message();
void save(Folder&);
void remove(Folder&);
const std::string& msg() const { return contents; }
void print_debug();
private:
void add_to_Folders(const Message&);
void remove_from_Folders();
void addFldr(Folder *f) { folders.insert(f); }
void remFldr(Folder *f) { folders.erase(f); }
void move_Folders(Message*); // define function to do the common work
private:
std::string contents;
std::set<Folder*> folders;
};
void swap(Message&, Message&);
class Folder {
friend void swap(Message&, Message&);
friend void swap(Folder &, Folder &);
friend class Message;
public:
explicit Folder(const std::string &str = "") :name(str) { }
Folder(const Folder &);
Folder& operator=(const Folder &);
Folder(Folder &&f); // need to update the Messages
Folder& operator=(Folder &&); // need to update the Messages
~Folder();
const std::string& fldr() const { return name; }
void print_debug();
private:
std::string name;
std::set<Message*> msgs;
void add_to_Message(const Folder&);
void remove_from_Message();
void addMsg(Message *m) { msgs.insert(m); }
void remMsg(Message *m) { msgs.erase(m); }
void move_Messages(Folder*); // define function to do the common work
};
void swap(Folder &, Folder &);
#endif
#include <iostream>
void swap(Message &lhs, Message &rhs)
{
using std::swap;
for (auto f : lhs.folders)
f->remMsg(&lhs);
std::cout << "Remove message from folders" << std::endl; // debug
for (auto f : rhs.folders)
f->remMsg(&rhs);
std::cout << "Remove message from folders" << std::endl; // debug
swap(lhs.folders, rhs.folders);
swap(lhs.contents, rhs.contents);
std::cout << "Message members swaped" << std::endl; // debug
for (auto f : lhs.folders)
f->addMsg(&lhs);
std::cout << "Added message to folders" << std::endl; // debug
for (auto f : rhs.folders)
f->addMsg(&rhs);
std::cout << "Added message to folders" << std::endl; // debug
}
Message::Message(const Message &m) : contents(m.contents), folders(m.folders)
{
add_to_Folders(m);
}
Message::~Message()
{
remove_from_Folders();
}
void Message::save(Folder &f)
{
folders.insert(&f);
f.addMsg(this);
}
void Message::remove(Folder &f)
{
folders.erase(&f);
f.remMsg(this);
}
void Message::add_to_Folders(const Message &m)
{
for (auto f : m.folders)
f->addMsg(this);
std::cout << "Added message to folders" << std::endl; // debug
}
void Message::remove_from_Folders()
{
for (auto f : folders)
f->remMsg(this);
std::cout << "Remove message from folders" << std::endl; // debug
}
Message& Message::operator=(const Message &rhs)
{
remove_from_Folders();
contents = rhs.contents;
folders = rhs.folders;
std::cout << "Message members assgined" << std::endl; // debug
add_to_Folders(rhs);
return *this;
}
void Message::print_debug()
{
std::cout << contents << ": ";
for (auto f : folders)
std::cout << f->fldr() << " ";
std::cout << std::endl;
}
void Message::move_Folders(Message *m)
{
folders = std::move(m->folders);
for (auto f : folders)
{
f->remMsg(m);
f->addMsg(this);
}
m->folders.clear();
}
Message::Message(Message &&m) : contents(std::move(m.contents))
{
move_Folders(&m);
}
Message& Message::operator= (Message &&rhs)
{
if (this != &rhs)
{
remove_from_Folders();
contents = std::move(rhs.contents);
move_Folders(&rhs);
}
std::cout << "Message members moved" << std::endl; // debug
return *this;
}
// Folder Implementation
void swap(Folder &lhs, Folder &rhs)
{
using std::swap;
for (auto m : lhs.msgs)
m->remFldr(&lhs);
std::cout << "clear folder" << std::endl; // debug
for (auto m : rhs.msgs)
m->remFldr(&rhs);
std::cout << "clear folder" << std::endl; // debug
swap(lhs.name, rhs.name);
swap(lhs.msgs, rhs.msgs);
std::cout << "Folder members swaped" << std::endl; // debug
for (auto m : lhs.msgs)
m->addFldr(&lhs);
std::cout << "Added messages to folder" << std::endl; // debug
for (auto m : rhs.msgs)
m->addFldr(&rhs);
std::cout << "Added messages to folder" << std::endl; // debug
}
void Folder::add_to_Message(const Folder &f)
{
for (auto m : f.msgs)
m->addFldr(this);
std::cout << "Added messages to folder" << std::endl; // debug
}
Folder::Folder(const Folder &f) : name(f.name), msgs(f.msgs)
{
add_to_Message(f);
}
void Folder::remove_from_Message()
{
for (auto m : msgs)
m->remFldr(this);
std::cout << "clear folder" << std::endl; // debug
}
Folder::~Folder()
{
remove_from_Message();
}
Folder& Folder::operator =(const Folder &rhs)
{
remove_from_Message();
name = rhs.name;
msgs = rhs.msgs;
std::cout << "Folder members assigned" << std::endl; // debug
add_to_Message(rhs);
return *this;
}
void Folder::print_debug()
{
std::cout << name << ": ";
for (auto m : msgs)
std::cout << m->msg() << " ";
std::cout << std::endl;
}
void Folder::move_Messages(Folder *f)
{
msgs = std::move(f->msgs);
for (auto m : msgs)
{
m->remFldr(f);
m->addFldr(this);
}
f->msgs.clear();
}
Folder::Folder(Folder&& f) : name(std::move(f.name))
{
move_Messages(&f);
}
Folder& Folder::operator=(Folder &&rhs)
{
if (this != &rhs)
{
remove_from_Message();
name = std::move(rhs.name);
move_Messages(&rhs);
}
std::cout << "Message members moved" << std::endl; // debug
return *this;
}