0

I am trying to write the solution for an exercise in C++ primer 5 ed. The program is writing two classes Message and Folder. each Folder has a set of pointers to a Message object and each Message has a set of pointers to Folders in which it exists so I wrote this code:

  • For brevity I didn't show the whole code so I'll try to focus on the part may causing problem:

    class Folder;
    class Message
    {
    public:
        Message(const std::string & = "");
        Message(const Message&);
        Message(Message&&);
        Message& operator=(const Message&);
        Message& operator=(Message&&);
        ~Message();
    
        const std::string& getMsg()const;
        void setMsg(const std::string&);
        void  save(Folder&);
        void remove(Folder&);
    
    //private:
        std::string msg_;
        std::set<Folder*> dirs_;
    
        void addToDirs(const Message&);
        void delFromDirs();
        void moveMsgs(Message&);
    
        friend void swap(Message&, Message&);
        friend class Folder;
    };
    
    class Folder
    {
    public:
        void addMsg(Message&);
        void delMsg(Message&);
    
    //private:
        std::set<Message*> msgs_;
    };
    
    Message::Message(const std::string& msg) : 
        msg_(msg)
    {}
    
    Message::Message(const Message& rhs) :
        msg_(rhs.msg_), 
        dirs_(rhs.dirs_)
    {
        addToDirs(rhs);
    }
    
    Message& Message::operator=(const Message& rhs)
    {
        delFromDirs();
        msg_ = rhs.msg_;
        dirs_ = rhs.dirs_;
        addToDirs(rhs);
    
        return *this;
    }
    
    
    void Message::addToDirs(const Message& msg)
    {
        for (auto& fdr : msg.dirs_)
            fdr->addMsg(*this);
    }
    
    Message::~Message()
    {
        delFromDirs();
    }
    
    void Message::save(Folder& fdr)
    {
        fdr.addMsg(*this);
    }
    
    void Message::remove(Folder& fdr)
    {
        fdr.delMsg(*this);
    }
    
    void Message::delFromDirs()
    {
        for (auto fdr : dirs_)
            fdr->delMsg(*this);
    }
    
    void Folder::addMsg( Message& msg)
    {
        msgs_.insert(&msg);
        msg.dirs_.insert(this);
    }
    
    void Folder::delMsg(Message& msg)
    {
        msgs_.erase(&msg);
        msg.dirs_.erase(this);
    }
    
    const std::string& Message::getMsg()const
    {
        return msg_;
    }
    
    void Message::setMsg(const std::string& msg)
    {
        msg_ = msg;
    }
    
    void Message::moveMsgs(Message& msg)
    {
        msg_ = std::move(msg.msg_);
        dirs_ = std::move(msg.dirs_);
    
        for (auto& fdr : dirs_)
        {
            fdr->delMsg(msg);
            fdr->addMsg(*this);
        }
        msg.dirs_.clear();
    }
    
    Message::Message(Message&& rhs) :
        msg_(std::move(rhs.msg_)),
        dirs_(std::move(rhs.dirs_))
    {
        std::cout << "Message's move-ctor\n";
        moveMsgs(rhs);
    }
    Message& Message::operator=(Message&& rhs)
    {
        std::cout << "=(Message&&)\n";
        if (this != &rhs) 
        {
            delFromDirs();
            moveMsgs(rhs);
        }
        return *this;
    }
    
    
    
    int main()
    {
    
        Folder f1, f2, f3;
    
        Message m1("Message m1"),
            m2("Message m2"), m3("Message m3"), m4("Message m4"),
            m5("Message m5");
    
        f1.addMsg(m1);
        f1.addMsg(m2);
        f1.addMsg(m5);
        f2.addMsg(m1);
        f2.addMsg(m4);
        f3.addMsg(m3);
    
    
        cout << "f1.msgs: \n";
        for (auto& msg : f1.msgs_)
            cout << msg->msg_ << endl;
        cout << endl;
    
    
    
    
        std::cout << "\nDone!\n";
    }
    
  • The problem is: The code works fine on VC++19 and compiled on wandbox.org using GCC-Head 10 and Clang 10 and works fine but I have an another compiler on my phone called "coding C++" it says:

"process completed (signal 11) press enter".

  • I want to know whether delFromDirs() is correctly handling deletion? Because the destructor of Message should remove each folder from its set of folders and each folder from them should remove that message from its set of messages.
Maestro
  • 2,512
  • 9
  • 24
  • 3
    Signal 11 is probably a segmentation fault, see https://en.wikipedia.org/wiki/Signal_(IPC)#Default_action. Setting aside whether it makes sense to code on a phone, the compiler that this app is using is most likely GCC or Clang. There aren't that many alternatives around. – walnut Feb 15 '20 at 22:18
  • @walnut: yes you are right. And it was complaining about "double deletion" or something like "memory corruption". I think the `delFromDirs()` is causing the problems. What do you think and suggest? Thanks – Maestro Feb 15 '20 at 22:20
  • Can you please explain the exercise? Not everyone has the book at hand. What seems immediately flawed is that you are violating the [rule of 0/3/5](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three). – walnut Feb 15 '20 at 22:23
  • 1
    Did run your code in a **debugger** to see where that error occurs, then run it again with a breakpoint near that failure so you can step carefully ahead and watch what happens leading up to that point? – tadman Feb 15 '20 at 22:29
  • @walnut: Oh sorry! It crushes also on MSVC in Debug g mode. – Maestro Feb 15 '20 at 22:32

1 Answers1

3

You are iterating over dirs_ in delFromDirs and you are calling fdr->delMsg(*this) which does

msgs_.erase(&msg);
msg.dirs_.erase(this);

The line msg.dirs_.erase(this) erases fdr from the dirs_ that you are iterating over. Since this is the element that you are currently iterating over, it will invalidate the current iterator of the range-for loop and then when the loop tries to increment the iterator, it will cause undefined behavior.

Don't remove elements from a container that you are currently iterating over, at least not the current element of a range-for loop. If you need to do that, you can take the return value from erase (which will be a valid iterator to the next element) and reset your loop iterator to continue from there. This then doesn't work with a range-for, though.

walnut
  • 21,629
  • 4
  • 23
  • 59
  • 1
    Oh great!!!! Yes I remember "we should not use range-for when we change the size of a container" is it true? Thank you very much! – Maestro Feb 15 '20 at 22:38
  • 2
    @Maestro Yes, at least removing the current element is always wrong. In some specific cases adding or removing other elements may be fine, but it is always tricky though. – walnut Feb 15 '20 at 22:39
  • Thank you guy you've saved the day after being exhausted and hopelessly searching. What confirms your saying this is from MSVC Debug mode: "Tree_unchecked_const_iterator& operator++() { if (_Ptr->_Right->_Isnil) { // climb looking for right subtree _Nodeptr _Pnode; while (!(_Pn" – Maestro Feb 15 '20 at 22:41
  • Could you give me a solution to this problem? – Maestro Feb 15 '20 at 22:42
  • 1
    @Maestro Just call `fdr->msgs_.erase(this);` for all folders in the destructor. There is no need to remove from `_dirs`. It will be destroyed after the destructor ran anyway. – walnut Feb 15 '20 at 22:45
  • 1
    Another thing is `addToDirs()` also has the same problem? I think it also uses a range-for on a container `set` and inserting to it which I think may invalidate iterators? – Maestro Feb 15 '20 at 22:46
  • 2
    @Maestro I don't think so. `insert` on a `std::set` does not invalidate any iterators. The only possible issue is that your loop now may also iterate over the newly inserted element. I think that cannot happen here, because you are only inserting pointers that have been there before and `std::set` does not save any duplicates. But as I said, reasoning about this is tricky, so you might want to be more explicit. – walnut Feb 15 '20 at 22:51
  • I've managed to do it as you've suggested. It works like a charm: I've used another scope in main creating another Message object , adding it to Folder f1 and prints the result in both inner scope and outer: What I see in the inner scope: I see message 7 in `f1` and outside that scope it doesn't exist which means it worked correctly. Thank you again. – Maestro Feb 15 '20 at 22:52