0

In the well-known C++ Primer book I found the following code snippet

Message& Message::operator=(const Message &rhs)
{
    // handle self-assignment by removing pointer before inserting them
    remove_from_Folders(); // updating existing Folders
    contents = rhs.contents; // copy message contents from rhs
    folders = rhs.folders; // copy Folder pointers from rhs
    add_to_Folders(rhs); // add this Message to those Folders
    return *this;
}

The Message class:

class Message {
    friend class Folder;
public: 
    // folders is implicity initialized to the empty set
    explicit Message(const std::string &str = ""):
        contents(str) { }
    // copy control to manage pointers to this Message
    Message(const Message&);           // copy constructor
    Message& operator=(const Message&) // copy assignment
    ~Message();                        // destructor
    // add/remove this Message from the specified Folder's set of messages
    void save(Folder&);
    void remove(Folder&);
private:
    std::string contents; // actual message text
    std::set<Folder*> folders; // Folder's 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 add_to_Folders(const Message&);
    // remove this Message from every Folder in folders
    void remove_from_Folders();
}

void Message::save(Folder &f)
{
    folders.insert(&f); // add the given Folder to our list of Folders
    f.addMsg(this);     // add this Message to f's set of Messages
}

void Message::remove(Folder &f)
{
    folders.erase(&f); // take the given Folder out of our list of Folders
    f.remMsg(this);    // remove this Message to f's set of Messages
}

// add this Message to Folders that point to m
void Message::add_to_Folders(const Message &m)
{
    for (auto f : m.folders) // for each Folder that holds m
        f->addMsg(this); // add a pointer to this Message to that Folder
}

void Message::remove_from_Folders()
{
    for (auto f : folders) // for each pointer in folders
        f->remMsg(this);   // remove this Message from that Folder
    folders.clear();       // no Folder points to this Message
}

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

Now my question: We see that in remove_from_Folders the whole folder set gets cleared. We also see that the rhs parameter from the copy-assignment operator is a reference. So why does calling remove_from_Folders() in it and afterwards doing folders = rhs.folders; in it work here? Since rhs is a reference, wouldn't rhs.folders now be the empty set when we use the same object (i.e. doing a self-(copy)assignment)? Isn't it that how references work? Or is there a special rule the compiler fulfills when operating with assignment-operators?

J.Doe
  • 19
  • 1
  • 2
  • 1
    If that's all the code there is and the `Folder` class doesn't do anything weird, then yes, a message's `folders` member will be empty after self-assignment. Usually copy-assignment operators do a check like `if (&rhs == this) return;` – Nelfeal Oct 16 '22 at 19:04
  • Thank you! I was really questioning my understanding of the things I have learned so far from the book... the authors might have forgotten that if statement I guess. – J.Doe Oct 16 '22 at 19:10
  • hard to comment/answer without reading the book (which i have in the first edition - don't remember this) and knowing the problem domain or anything regarding the Folder class. – Neil Butterworth Oct 16 '22 at 19:18
  • @J.Doe `Message& operator=(Message m) { std::swap(contents, m.contents); std::swap(folders, m.folders); return *this; }` -- If the copy constructor does not call the assignment operator, then this code should work without having to check for self-assignment. – PaulMcKenzie Oct 16 '22 at 19:35
  • Just to throw in a monkey wrench, there is C++ Primer and C++ Primer Plus. Funny, huh? – user4581301 Oct 16 '22 at 20:30
  • Opinion: In most cases of a non-default assignment operator that isn't implemented with copy-and-swap I consider self-copy to be something in need of fixing. Rather than test for-and-ignore, I generally pop in an `abort`. – user4581301 Oct 16 '22 at 20:34
  • 1
    @user4581301 That's an interesting idea (or an `assert`) – Paul Sanders Oct 16 '22 at 21:03
  • 1
    @PaulSanders `assert`, `abort`, I'm surprised I can still keep my head on straight. When I compiled the `abort` as I intended to write it, the compiler would catch the mistake damn fast. – user4581301 Oct 17 '22 at 04:10
  • @user4581301 Hehe, me too x – Paul Sanders Oct 17 '22 at 14:19

0 Answers0