Iterators validity and threads - c++

Let's say that in the main thread of a C++11 program I build an std::set, fill it with items and extract an iterator it from it. After that, from another thread, I start modifying the set in such a way that elements can only be added to it but not erased.
Is the validity of it assured also while the set is being modified, or should I consider it invalid while the set is being modified by insertion operations from the other thread?

From section 23.2.1 [container.requirements.general]:
Unless otherwise specified (either explicitly or by defining a function in terms of other functions), invoking a container member function or passing a container as an argument to a library function shall not invalidate iterators to, or change the values of, objects within that container.
For associative containers such as std::set, section 23.2.4 ([associative.reqmts]) says:
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.
So your iterators will remain valid after inserting additional items.
However, thread safety is a different topic completely.
Section 17.6.5.9 ([res.on.data.races]) provides that
Operations on iterators obtained by calling a standard library container or string member function may access the underlying container, but shall not modify it.
Since that results in reading the container while it's being updated, it is not necessarily safe to use a std::set iterator while inserting into the collection from another thread. Your implementation may provide a stronger guarantee.

Related

Does a C++ STL Map move a value's location around after creation?

I have read some hints here and there that after inserting an object into a c++ stl map, then as long as one doesn't delete it, its location in memory never changes. But nobody ever mentioned any literature or sources to back it up, so I don't know how reliable such hints are. Can anyone answer this definately/reliably? Could it be implementation-dependent? Is there a guarantee anywhere?
Does a C++ STL Map move a value's location around after creation?
No.
Can anyone answer this definately/reliably?
You can rely on it.
Could it be implementation-dependent?
It couldn't be dependent on implementation.
Is there a guarantee anywhere?
Yes, it is guaranteed in the C++ standard:
[container.rev.reqmts]
Unless otherwise specified (either explicitly or by defining a function in terms of other functions), invoking a container member function or passing a container as an argument to a library function shall not invalidate iterators to, or change the values of, objects within that container.
[associative.reqmts.general]
The insert, insert_­range, 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.
The extract members invalidate only iterators to the removed element; pointers and references to the removed element remain valid.
However, accessing the element through such pointers and references while the element is owned by a node_­type is undefined behavior.
References and pointers to an element obtained while it is owned by a node_­type are invalidated if the element is successfully inserted.

Using the address of an item in stable vector

items in std::vector are dynamically allocated and their addresses may change when a reallocation happens. So, it is not possible to depend on their addresses because it is not stable.
On the other hand, if I have a std::vector which contains some items and I do not have any intention to change anything about it during its life cycle, is it valid (well-defined) to use the addresses of its items?
Example:
std::vector<foo> foos;
foos.reserve(100);
for(size_t i=0;i<100;++i){
foos.emplace_back(make_random_foo());
}
//From now no one can touch foos
auto ptr_to_the_fifth_foo=&foos[4];
In other words, does the standard guarantee that noting will affect the vector items addresses since I did not do that by my self?
If no member function of the std::vector is called, the vector may not be changed at all and as such the contents remain the same and all pointers stay valid.
In your example you call operator[](size_type n) which is defined in the standard as being equivalent to *(a.begin() + n).
A std::vector is a container and therefore, the container requirements hold which state:
Unless otherwise specified (either explicitly or by defining a function in terms of other functions), invoking a container member function or passing a container as an argument to a library function shall not invalidate iterators to, or change the values of, objects within that container.
Since begin() is not specified to invalidate any iterators to the container, operator[] won't either.
Yes.
Pointers and references to elements are only invalidated when their iterator is invalidated.
Iterators are invalidated when capacity has to grow (when size passes capacity), or when you insert/remove elements before that element in the vector. They can also be invalidated when a container is moved-to or moved-from, but it might not occur.
I believe swap is specified to not invalidate iterators, but rather make them refer to the "new home" as it where (and hence, the pointers/references to the "new home" in the "new vector") (ie, the buffer ownership changes). Move-assign does not make this promise. I do not remember off the top of my head if move-construct does.

What section of the C++ standard requires that set::erase calls destructors promptly

What section of the C++11 standard (here's a copy of a draft standard) requires associative containers like std::set, std::map, std::unordered_set, and std::unordered_map to immediately call destructors of objects that are erased from them?
To put it another way - are standard-compliant associative containers allowed to delay (not elide!) their calls to the key and/or value destructors of the keys and values they store?
If not, what section in the standard forbids it?
I ask because I am interested in lazy deletions (sometimes called weak deletions) in associative containers. This is a method of "erasing" a key (or key/value pair) from a structure in which the actual data remains in place, but the node containing it is marked as dead. These are sometimes called tombstones. They are used in many theory papers about data structures, and sometimes used in practice.
A very simple example is deletion in open-addressed hash tables, which is sometimes implemented with tombstones. When the hash table is eventually rebuilt, all the destructors are called and the tombstoned key/value pairs can be actually and finally deleted and deallocated.
There are general requirements in the table for associative containers which describe the requirements for erase calls.
E.g. a.erase(q) | erases the element pointed to by q.
The element type for map is a pair of a key and a value. There is no sensible interpretation of "erases" that doesn't involve the proper destruction of the element (key and value). I doubt there is anything more explicitly worded for this situation in the standard.

std::map thread-safety

Is reference to object in std::map is thread safe?
std::map< std::string, Object > _objects;
map can be changed from many threads and this access is synchronized, but reference to value (Object &) accessable just from 1 instance and thread. is write operations with Object & is safe if another thread will add items to map? will it reallocate?
The C++11 standard guarantees that const method access to containers is safe from different threads (ie, both use const methods).
In addition, [container.requirements.dataraces] states
implementations are required to avoid data races when the contents of
the contained object in different elements in the same sequence,
excepting vector<bool>
In other words, except for vector<bool> modifying distinct contents is not a data race.
Now, if one thread invalidates an iterator used by another thread, clearly this is a data race (and results in undefined behavior). If one thread does non-const access to a container, and another does const access, that is a data race (and undefined behavior). (Note: a number of functions are "considered const" for the purpose of multithreading, including begin, end and other functions (and methods) that are non-const simply because they return non-const iterators. [] is included in this set of pseudo-const for thread safety reasons, except for map and unordered_set etc -- 23.2.2.1).
However, it appears that if you have a reference to an element within the container, and engage in operations that do not invalidate that reference in another thread, and never write to that element in another thread, you can safely read from that reference. Similarly, if other threads never even read from the element, then writing to that element shouldn't result in undefined behavior.
For standards references, 17.6.5.9.5 seems to guarantee that functions from the standard library won't run away and read/write elements needlessly.
So the short answer: you are safe, so long as the other thread doesn't directly mess with that particular entry in the map.
Elements in a map are stable, they do not get moved or invalidated unless the element is erased from the map. If only one thread is writing to a given object, and changes to the map itself are correctly synchronized, then I believe it will be safe. I'm sure it's safe in practice, and I think it's safe in theory too.
The standard guarantees that distinct elements can be modified by different threads, in [container.requirements.dataraces]
Notwithstanding (17.6.5.9), implementations are required to avoid data races when the contents of the contained object in different elements in the same sequence, excepting vector<bool>, are modified concurrently.
This only allows you to modify the elements, not to insert new elements into the map while modifying elements. For some containers, such as std::vector, modifying the vector itself might also modify elements by reallocating and moving them, but [associative.reqmts]/9 ensures std::map won't invalidate existing elements.
Since no member function of std::map is required to access the second member of its elements (i.e. the mapped_type) I think [res.on.data.races]/5 says no other thread will conflict with writes to that member when modifying the map. (Thanks to Yakk for that final piece of the puzzle)

Should std::vector::swap() with stateful allocators invalidate all iterators?

Given allocators a1 and a2, where a1 != a2,
and std::vectors v1(a1) and v2(a2)
then v1.swap(v2) invalidates all iterators.
Is this expected behavior?
In general, swap never invalidates iterators. However, another rule comes into play when the allocators are different. In that case the behavior depends on allocator_traits<a1>::propagate_on_container_swap::value and allocator_traits<a2>::propagate_on_container_swap::value. If both are true, the allocators are exchanged along with the data, all iterators remain valid. If either is false, behavior is undefined, so the particular behavior exhibited by VC++ 2010 is allowed.
From [container.requirements.general] (wording from n3290):
Allocator replacement is performed by copy assignment, move assignment, or swapping of the allocator only if allocator_traits<allocatortype>::propagate_on_container_copy_assignment::value,
allocator_traits<allocatortype>::propagate_on_container_move_assignment::value, or allocator_traits<allocatortype>::propagate_on_container_swap::value is true within the implementation of the corresponding container operation. The behavior of a call to a container’s swap function is undefined unless the objects being swapped have allocators that compare equal or allocator_traits<allocatortype>::propagate_on_container_swap::value is true.
and
Every iterator referring to an element in one container before the swap shall refer to the same element in the other container after the swap
and
Unless otherwise specified ... no swap() function invalidates any references, pointers, or iterators referring to the elements of the containers being swapped.
23.3.6.5 does not specify alternate rules for vector::swap().