6

Some binary tree data structures (such as Splay trees) will re-balance on reads to move recently accessed items toward the root, such that the subsequent look-up time may be reduced.

Are the standard containers (std::map, std::set) allowed to do this?

At least one concern is thread safety. Previously, I'd thought that as long as you were only doing read-only operations on standard containers, it was safe to do this from multiple threads without introducing mutexes/locks etc. Maybe I need to re-think this?

I know that typically red-black trees are used for the standard tree containers, and that these data structures aren't usually modified on reads. But would a hypothetical implementation that did modify be conforming?

My c++-standards-foo needs improvement, but I'm not sure whether the current standard addresses thread-safety for containers. Is this different in c++0x?

templatetypedef
  • 328,018
  • 92
  • 813
  • 992
Darren Engwirda
  • 6,660
  • 3
  • 19
  • 41
  • Isn't this implementation dependant? doesn't the standard merely define an interface and expected behaviour? – hookenz Aug 29 '11 at 04:07
  • @Matt H: My thinking is that this would be "observable behaviour" (or however the standard puts it...) - so potentially something that should be addressed in a standardised way. Part of my question is whether it is addressed or not? – Darren Engwirda Aug 29 '11 at 04:12

2 Answers2

8

From the c++0x draft:

§23.2.2/1:

For purposes of avoiding data races (17.6.5.9), implementations shall consider the following functions to be const: begin, end, rbegin, rend, front, back, data, find, lower_bound, upper_bound, equal_range, at and, except in associative or unordered associative containers, operator[].

Note that c++03 does not say anything about multi-threading, but as you say, most implementations use RB-trees, which will not rebalance on a read operation.

Mankarse
  • 37,343
  • 9
  • 88
  • 138
  • 1
    So, if I have it right - since `find` is `const` there is no re-balancing allowed on reads. The fact that `operator[]` is not `const` is just because it's allowed to insert a default item if no match is found. But this is only for `c++0x`... – Darren Engwirda Aug 29 '11 at 04:24
  • @Darren Engwirda- Not exactly. There could still be a rebalance on a read, as long as it was implemented with an atomic commit. It shouldn't make any difference to your code though. – Mankarse Aug 29 '11 at 04:29
  • 1
    @Darren: `operator[]` is not on that list because `operator[]` will _insert_ an element into the tree if it is not found. It is a modifying operation. – Nicol Bolas Aug 29 '11 at 05:15
  • @Mankarse: Sorry if I'm not getting it, but if we have `find() const { // etc... }` doesn't the `const` aspect prevent any modification of the underlying tree structure. How could this allow modifications - even if they were thread-safe?? – Darren Engwirda Aug 29 '11 at 21:58
  • 1
    @Darren - When I said that it could modify the structure I was being pedantic. It is possible that an implementation could use `mutable` members and perform thread-safe rebalancing, but if this did happen, it would have to be in a way that would not change the meaning of your coded. Honestly, I think that it is such an unlikely implementation that the possibility could just be ignored. – Mankarse Aug 30 '11 at 01:03
  • @DarrenEngwirda "_doesn't the const aspect prevent any modification of the underlying tree structure._" It does not. In this context **`const` is a promise that the behaviour of an object will not change.** Since "behaviour" is not formally defined, it is an informal promise. (Within a good specification formalism, you may be able to formally express "behaviour is not changed". But this is entirely outside the formalism that is the C++ language.) – curiousguy Oct 01 '11 at 01:54
  • @Mankarse "_It is possible that an implementation could use mutable_" Possible but not necessary. For you, **the user**, `const` is only a promise the "value" will not change. For you `mutable` is irrelevant. OTOH you would care about what might create a **data race**, if you are doing MT programming. – curiousguy Oct 01 '11 at 02:02
3

Read functions on maps etc. are required to have a const function defined. Hence you get the guarantee that the object hasn't changed.

This is true both for C++11 ( 23.4.4.1 ) as well as C++03 ( 23.3.1 ).

23.2.2 of the new C++11 standard may be of special interest here:

  1. For purposes of avoiding data races (17.6.5.9), implementations shall consider the following functions to be const: begin, end, rbegin, rend, front, back, data, find, lower_bound, upper_bound, equal_range, at and, except in associative or unordered associative containers, operator[].

  2. 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.

Jason
  • 30,174
  • 7
  • 55
  • 73
Kornel Kisielewicz
  • 51,225
  • 12
  • 100
  • 147
  • `const` does not mean what you think it does. See the other comments. – curiousguy Oct 01 '11 at 02:04
  • @curiousguy, I know what it means. But for typical circumstances, the informal promise is all we need. – Kornel Kisielewicz Oct 01 '11 at 10:40
  • A class can implement a cache, and keep its const member functions const (hide the internal state changes), and still be MT-safe (avoid the data races with concurrent calls of const member functions), although this can be tricky. – curiousguy Oct 02 '11 at 05:30