Peculiar behaviour while erasing an element from from std::multimap - c++

I was trying to remove an element from a std::multimap while I am looping over it in a thread that manipulates it . I used erase function in the following ways
When I do this
//mItr is base iterator which loops over the multimap
std::multimap<std::string, std::string>::iterator tmpItr = ++mItr;
healthyQ.erase(mItr);
mItr = tmpItr;
so that I can have validated iterator after erasing the element from multimap, the program stalls in erase() call . So I used it in the following way to get the next valid iterator :
mItr = healthyQ.erase(mItr);
It worked. It consumed a lot of time and I am still not sure where the problem can be

The second way is exactly how it is supposed to work
When you hold an iterator to a tree-based container and you erase it, it alters the pointers between the various nodes pointing to this node (and others). Even if you would know exactly what this node is (through the iterator), you are left with no indication what is the next node (and consequently, the next iterator). Because of this, the erase method first finds the next node, performs the erase, and then returns an iterator to this next node.
You can see here how removal works in a red-black tree.

You invalidated the iterator by calling erase() function. So when you capture the iterator returns from erase() and reuse later you are properly handling the iterator and avoiding iterator invalidation.

Related

Get pointer to node in std::list or std::forward_list

I am planning to use std::list in my code, I decided not to use std::forward_list, because for deletions (I figured) the whole list will have to traversed, O(N) complexity for std::forward_list (being a single link list). However, when I looked into the documentation I noticed both the stl containers have O(N) complexity to remove an item.
http://www.cplusplus.com/reference/forward_list/forward_list/remove/
http://www.cplusplus.com/reference/list/list/remove/
After some thinking I figured out why (I think). It's because in both cases, the whole list has to be scanned to find the node first, and then delete it. Is this right?
I then looked into the "erase" and "erase_after" methods, and their complexity is "Linear in the number of elements erased (destructions).". It's because, I am passing an iterator to the node (which is kind of like a "pointer"). However, I cannot (or prefer not to) pass this iterator around in my code to access the data in the node. I am not sure if this iterator will be valid if the list is modified? Thoughts?
My question is, is there a way I can get a pointer to the node in the list. That way, I know it will be valid throughout the lifetime of my program, pass it around. And I can just look into it to get access to my data.
However, I cannot (or prefer not to) pass this iterator around in my code to access the data in the node.
Why not? Iterators are easy to use and are quite lightweight. A pointer isn't better in any way.
I am not sure if this iterator will be valid if the list is modified?
For list, any iterator will remain valid, even if the list is modified. Except, of course, if you erase the particular element that is the iterator points to. But that's kind of obvious, you can' expect to have an iterator (or pointer) to something that doesn't exist any more.
(vector is more dangerous. One small change to a vector can invalidate all its iterators.)
You can take a pointer to any individual element in the list.
list<int> iterator it = find(l.begin(), l.end(), 7); // get an iterator
int * ptr = &*it; // get a pointer to the same element.
The pointer is similar to the iterator in many respects. But the iterator is a little more powerful. An iterator can be incremented or decremented, to access neighbouring elements in the list. And an iterator can be used to delete an element from the list. A pointer cannot do either of those things.
Both the iterator and pointer remain valid as long as that particular element isn't removed.
I am not sure if this iterator will be valid if the list is modified
Yeah, in the general case, storing iterators is risky unless you keep a close eye on the operations performed on your container.
Problem is, this is just the same for a pointer. In fact, for many containers, iterators are implemented as pointers.
So either store an iterator or a pointer if you like but, either way, keep an eye on the iterator invalidation rules:
Iterator invalidation rules
For lists, an iterator is valid even if other items in the list are erased. It becomes garbage when that item the iterator references in the list is removed.
So, as long as you know the iterator you're passing around isn't being removed by some other piece of code, they're safe to hold onto. This seems fragile though.
Even if there was a construct outside of iterators to reference a node in the list, it would suffer from the same fragility.
However, you can have each node contain an std::shared_ptr to the data it stores instead of the object itself and then pass around std::weak_ptr's to those objects and check for expired before accessing those weak_ptr's.
eg
instead of
std::list<MyClass> foo;
you would have
std::list<std::shared_ptr<MyClass>> foo;
have a look here for info on weak_ptr's
is there a way I can get a pointer to the node in the list
Yes, in your particular implementation.
No, in a standard-compliant way.
If you look at the std::list documentation, there is not a single word about a node. While it is hard to imagine a different way to implement the std::list other than using a doubly linked list, there is nothing that prevents it.
You should almost never come into any contact with undocumented internals of libraries.
Adding, removing and moving the elements within the list or across several lists does not invalidate the iterators or references. An iterator is invalidated only when the corresponding element is deleted.
Source: https://en.cppreference.com/w/cpp/container/list
So a std::list<>::iterator is only invalidated when the corresponding element is deleted. So yes, as long as you make sure that the corresponding element exists (which you will anyway have to do in your scenario of storing/passing around a pointer to anything) you can save and/or pass around the iterator throughout the lifetime of your program.
Now, an iterator is nothing but a pointer in disguise. So, if you prefer to save/pass around the corresponding pointer instead of iterator, you can always first convert the iterator to the pointer as #Aaron McDaid suggested.
int * ptr = &*it; // get a pointer to the same element.

What are end() iterators in a set data structure supposed to return?

This is indeed Programming HW, but I'm not asking for a code, but rather what am i suppose to be returning when i call iterator function end()? The max value/Right-Most set node?
I believe for begin(), we just get the iterator for root, right, since it is the first thing we inputted.
Also for the operator++ in this Set Data Structure iterators, We just going to increment the iterators depending on the traversal of trees starting from root?
Thank for helping.
Traditionally end() returns one-past the end of a structure. Otherwise you wouldn't be able to traverse the entire structure with a current != end() loop. This is the case for a std::vector, however iterators are more complex for more complex data structures. But you should always be able to get to end() by using operator++ on the iterator that points to the final element in your iterable structure.
You can not dereference the end() element, that is typically undefined behaviour.
If you are implementing the data structure yourself, you can decide what end() means. It has to be an iterator of the correct type, and a value that you can match with the result of incrementing a normal iterator beyond the last element in the set. Because iterators into sets are bidirectional, the specific value that you return needs to be able to walk to the previous value in the set.
A common approach is to use a sentry node in the container that holds the pointers to the first and last elements in the data structure, and use an iterator into that node (that does not hold a proper value) as the end() iterator.
In the case of begin(), it does not represent the value at the root of the tree, but rather the smallest value, so it will be the leftmost node in the tree.

Continuing to iterate through std::map after insertion

This question clarified that iterators remain valid after insertion. I'd like to go a bit further and ask for verification that this is expected behavior:
std::map automatically sorts based on key.
insertion into the map thus automatically places the new element in a sorted location somewhere inside the map
An iterator++ operation therefore could return a different element after insertion than it would have prior to insertion.
Does this all make sense?
this's true if the new inserted element is after the iterator you are visiting.

Accessing the head from anywhere STL list

In order to get the next element in the list one just needs to increase the iterator. However from any element in the list, is there a way to get directly to the head of a list? For example if iterator is pointing to the third element of a list, is there a way to get to the front of the list besides iterating backwards ?
Thanks
No, because std::list is designed to model a doubly-linked list (and is usually implemented as one), and in a doubly linked list, each element only has pointers to the previous and next element in the list.
No, and you cannot even do it just based on having one single iterator: You have no way of knowing whether the iterator is valid, and whether you are allowed to increment or decrement it!
The only way you could do it would be by comparing your iterator (presumed valid) to x.begin() and x.end(), but once you have those, you already have the iterator to the list head and the question becomes moot.
Iterators should always be thought of as coming in pairs [first, last), and containers provide such a pair with their begin()/end() member functions.

C++: how to track a pointer to a STL list element?

I would like to track a pointer to a list element for the next read access. This pointer would be advanced every time the list is read. Would it be bad practice to cache an iterator to the list as a member variable, assuming I take proper precautions when deleting list elements?
An iterator to a list element remains valid until the element it refers to is removed. This is guaranteed by the standard.
There is no problem with caching an iterator as long as you make sure you don't remove elements from the list or refresh the cached iterator when you do.
Iterators are meant to be used. They aren't just for looping over each element in a for-loop. All you have to take into account is the rules for iterator invalidation.
std::vector iterators can be invalidated by any operation that inserts elements into the list. The iterators for all elements beyond the point of insertion are invalidated, and all iterators are invalidated if the insertion operation causes an increase in capacity. An operation that removes an element from the vector will invalidate any iterator after the point of removal.
std::deque iterators are invalidated by any operation that adds or removes elements from anywhere in the deque. So it's probably not a good idea to keep these around very long.
std::list, std::set, and std::map iterators are only invalidated by the specific removal of the particular element that the iterator refers to. These are the longest-lived of the iterator types.
As long as you keep these rules in mind, feel free to store these iterators all you want. It certainly isn't bad form to store std::list iterators, as long as you can be sure that that particular element isn't going anywhere.
The only way you're going to be able to properly advance though an STL std::list<T> in a platform and compiler independent way is through the use of a std::list<T>::iterator or std::list<T>::const_iterator, so that's really your only option unless you're planning on implementing your own linked-list. Per the standard, as others here have posted, an iterator to a std::list element will remain valid until that element is removed from the list.