Is it safe to reference a value in unordered_map - c++

For example:
#include <unordered_map>
class A{};
std::unordered_map<unsigned int, A> map {{0,{}},{1, {}},{2, {}}};
int main() {
A& a1 = map[1];
// some insert and remove operations ( key 1 never removed)
// ....
}
Is it safe to still use a1 to reference the value which key is "1", after a lot of insert operations?
In other word:
since std::vector will move elements if the capacity changed, a reference of it's element is not guarantee to be valid. Is this fact also fits for unordered_map?

Yes, it is safe. From the standard:
22.2.7 Unordered associative containers [unord.req]
The insert and emplace members shall not affect the validity of
references to container elements, but may invalidate all iterators to
the container. The erase members shall invalidate only iterators and
references to the erased elements, and preserve the relative order of
the elements that are not erased.
References are safe, but iterators are not!

Perhaps a "less authoritative" source, but easier to read:
References to elements in the unordered_map container remain valid in
all cases, even after a rehash
https://www.cplusplus.com/reference/unordered_map/unordered_map/operator[]/

after a lot of insert operations?
If you meant insert or emplace, then it's fine.
(emphasis mine)
If rehashing occurs due to the insertion, all iterators are invalidated. Otherwise iterators are not affected. References are not invalidated. Rehashing occurs only if the new number of elements is greater than max_load_factor()*bucket_count(). If the insertion is successful, pointers and references to the element obtained while it is held in the node handle are invalidated, and pointers and references obtained to that element before it was extracted become valid. (since C++17)

Related

Does set insertion invalidates for each loop?

Let's look at the following example:
set<int> candidates;
for (int candidate : candidates) {
if (candidate == target) {
candidates.erase(target);
}
}
I would like to know if the iterators gets invalidated, and wonder if it does affect the for-each loop.
because according to the standards, in the insert function it says:
No iterators or references are invalidated.
If the insertion is successful, pointers and references to the element obtained while it is held in the node handle are invalidated, and pointers and references obtained to that element before it was extracted become valid. (since C++17)
I would like to know if the iterators gets invalidated,
Yes. std::set::erase invalidates iterators to the erased element.
and wonder if it does affect the for-each loop.
Yes. The behaviour is undefined if the erase is called in the example.
The loop is entirely unnecessary. You can instead use just a single call to candidates.erase(target).
according to the standards, in the insert function it says:
What the standard says about insert function is largely irrelevant when you don't insert.

When no iterators are invalidated, does this include the end iterator?

A std::maps iterators stay valid when inserting elements, eg:
std::map<std::string,int> my_map;
my_map["foo"] = 1;
my_map["bar"] = 2;
auto it_foo = my_map.find("foo");
auto it_bar = my_map.find("bar");
my_map["foobar"] = 3;
after inserting another element (in the last line) the two iterators are still valid. How about the end ? For example:
auto it_end = my_map.find("something that isnt in the map");
my_map["barfoo"] = 4; // does not invalidate iterators
assert(it_end == my_map.end()); // ??
In other words: If a method does not invalidate iterators (other than those explicitly mentioned, as for example in case of map::erase) does this mean that also the end is guaranteed to be the same before as after calling the method?
PS: I am aware that I could just try and see, but this wont tell me whether I can rely on this behaviour.
PPS: For example pushing into a std::vector invalidates all iterators, or only the end (when no reallocation took place), but in this case the docs explicitly mention the end. Following this reasoning, "no iterators are invalidated" should include end, but I am not 100% convinced ;)
N4140 23.2.4 Associative containers [associative.reqmts][1]
9 The insert and emplace members shall not affect the validity of iterators and references to the container, and the erase members shall invalidate only iterators and references to the erased elements.
Definitely the term iterators refers to all iterators including end.

std::list - are the iterators invalidated on move?

std::list iterators have some very nice properties - they remain valid when any other element is removed, when a new element is added and even when 2 lists are swapped (Iterator invalidation rules)!
Considering following code behaviour and that the iterators are implement by a form of pointer to the actual node which doesn't change when the list is moved, my guess is that the iterators are still valid in the new container when a std::list is moved, but also I can be in the UB area here by accessing invalid memory which actually has the "expected" value.
std::list<int> l1{3, 2, 1};
std::list<int> l2;
auto it = std::prev(l1.end());
std::cout<<l1.size()<<" "<<l2.size()<<" "<<*it<<std::endl;
l2 = std::move(l1);
std::cout<<l2.size()<<" "<<*it<<std::endl;
3 0 1
3 1
Is it guaranteed by the standard if the iterators remain valid when std::list is moved? What about other containers?
For containers in general, only swap guarantees that iterators remain valid (and point into the swapped containers).
For std::list, the special member function splice() guarantees that iterators retain their expected meaning.
In general, constructing a container from an rvalue doesn't make guarantees about iterators; the only general requirement is that the new container has the "same value" as the container it was constructed from had originally.
(You can imagine debug iterator implementations that store a reference to the container, and that reference would become dangling after a move.)

Can we store unordered_map<T>::iterator?

Reference http://www.careercup.com/question?id=17188673 by chetan.j9
void Insert( string s ) {
if( IsElementPresent(s) )
return;
myMap[s] = myMapVector.size();
unordered_map<string,int>::iterator it = myMap.find(s);
myMapVector.push_back(it);
}
Question> Can we store the iterator of unordered_map for later retrieval? Based on my understanding, the iterator will be invalidated after the insertion or deletion of an element.
Thank you
#syam's answer is correct (+1), but I think it's useful to quote from the only authoritative source, the C++11 Standard:
(§23.2.5/13) The insert and emplace members shall not affect the validity of references to container elements, but may invalidate all iterators to the container. The erase members shall invalidate only iterators and references to the erased elements.
(§23.2.5/14) The insert and emplace members shall not affect the validity of iterators if (N+n) < z * B, where N is the number of elements in the container prior to the insert operation, n is the number of elements inserted, B is the container’s bucket count, and z is the container’s maximum load factor.
(To put this in context: §23.2.5 is the section on unordered associative containers, so it applies to std::unordered_set, std::unordered_map, std::unordered_multiset and std::unordered_multimap.)
This means:
If you want to insert n elements into an unordered_map called hash, you can check whether
hash.size() + n < hash.max_load_factor() * hash.bucket_count()
is true. If it is false, all iterators will be invalidated during the insert. If true, iterators will remain valid.
Even if iterators are invalidated in this operation, references to the elements themselves will remain valid.
If you erase elements, only iterators pointing to those will be invalidated; other iterators will remain valid.
Insertion of an item invalidates all iterators only if a rehashing occurs (ie. if the new number of elements is greater than or equal to max_load_factor()*bucket_count()). Otherwise no iterators are invalidated.
Removal of an item only invalidates the iterators to the removed element(s) not to other unrelated elements.
Source: std::unordered_map::insert and std::unordered_map::erase.
Of course these rules apply only to std::unordered_map. Other containers may have different invalidation rules, it's up to you to look it up in the documentation.

Persistant references in STL Containers

When using C++ STL containers, under what conditions must reference values be accessed?
For example are any references invalidated after the next function call to the container?
{
std::vector<int> vector;
vector.push_back (1);
vector.push_back (2);
vector.push_back (3);
vector[0] = 10; //modifies 0'th element
int& ref = vector[0];
ref = 10; //modifies 0'th element
vector.push_back (4);
ref = 20; //modifies 0'th element???
vector.clear ();
ref = 30; //clearly obsurd
}
I understand that in most implementations of the stl this would work, but I'm interested in what the standard declaration requires.
--edit:
Im interested becuase I wanted to try out the STXXL (http://stxxl.sourceforge.net/) library for c++, but I realised that the references returned by the containers were not persistent over multiple reads, and hence not compatible without making changes (however superficial) to my existing stl code. An example:
{
std::vector<int> vector;
vector.push_back (1);
vector.push_back (2);
int& refA = vector[0];
int& refB = vector[1]; //refA is not gaurenteed to be valid anymore
}
I just wanted to know if this meant that STXXL containers where not 100% compatible, or indeed if I had been using STL containers in an unsafe/implementation dependant way the whole time.
About inserting into vectors, the standard says in 23.2.4.3/1:
[insert()] causes reallocation if the
new size is greater than the old
capacity. If no reallocation happens,
all the iterators and references
before the insertion point remain
valid.
(Although this in fact this talks about insert(), Table 68 indicates that a.push_back(x) must be equivalent to a.insert(a.end(), x) for any vector a and value x.) This means that if you reserve() enough memory beforehand, then (and only then) iterators and references are guaranteed not to be invalidated when you insert() or push_back() more items.
Regarding removing items, 23.2.4.3/3 says:
[erase()] invalidates all the
iterators and references after the
point of the erase.
According to Table 68 and Table 67 respectively, pop_back() and clear() are equivalent to appropriate calls to erase().
Some basic rules for vector:
Reallocation invalidates all
references, pointers, and iterators
for elements of the vector.
Insertions may invalidate references,
pointers, and iterators.
Inserting or removing elements
invalidates references, pointers, and
iterators that refer to the following
elements.
If an insertion causes reallocation,
it invalidates all references,
iterators, and pointers.
I expect that references would be invalidated only by any explicit or implicit resize() (see also the max_size, capacity, and reserve methods).
Vector will invalidate its iterator and references when it reallocates, which depends upon its current capacity. Although the above code might work in some cases, you shouldn't rely on this as the reference might be invalidated after the push_back(4) call.