Reputation: 1610
I'm working with a std::map<std::string, MyClass* >
.
I want to test if my_map.find(key)
returned a specific pointer.
Right now I'm doing;
auto iter = my_map.find(key);
if ((iter != my_map.end()) && (iter->second == expected)) {
// Something wonderful has happened
}
However, the operator *
of the iterator is required to return a reference. Intuitively I'm assuming it to be valid and fully initialized? If so, my_map.end()->second
would be NULL
, and (since NULL
is never expected), I could reduce my if statement to:
if (iter->second == expected)
Is this valid according to specification? Does anyone have practical experience with the implementations of this? IMHO, the code becomes clearer, and possibly a tiny performance improvement could be achieved.
Upvotes: 13
Views: 5535
Reputation: 16670
If iter == my_map.end ()
, then dereferencing it is undefined behavior; but you're not doing that here.
auto iter = my_map.find(key);
if ((iter != my_map.end()) && (iter->second == expected)) {
// Something wonderful has happened
}
If iter != my_map.end()
is false, then the second half of the expression (iter->second == expected
) will not be exectuted.
Read up on "short-circut evaluation". Analogous valid code for pointers:
if ( p != NULL && *p == 4 ) {}
Upvotes: 2
Reputation: 33998
Even without checking the specs, you can easily see that dereferencing an iterator at end
has to be invalid.
A perfectly natural implementation (the de-factor standard implementation for vector<>
) is for end()
to be literally a memory pointer that has a value of ptr_last_element + 1
, that is, the pointer value that would point to the next element - if there was a next element.
You cannot possibly be allowed to dereference the end
iterator because it could be a pointer that would end up pointing to either the next object in the heap, or perhaps an overflow guard area (so you would dereference random memory), or past the end of the heap, and possibly outside of the memory space of the process, in which case you might get an Access Violation exception when dereferencing).
Upvotes: 2
Reputation: 126432
Intuitively I'm assuming it to be valid and fully initialized?
You cannot assume an iterator to an element past-the-end of a container to be dereferenceable. Per paragraph 24.2.1/5 of the C++11 Standard:
Just as a regular pointer to an array guarantees that there is a pointer value pointing past the last element of the array, so for any iterator type there is an iterator value that points past the last element of a corresponding sequence. These values are called past-the-end values. Values of an iterator
i
for which the expression*i
is defined are called dereferenceable. The library never assumes that past-the-end values are dereferenceable. [...]
Upvotes: 14
Reputation: 4463
However, the operator *of the iterator is required to return a reference. Intuitively I'm assuming it to be valid and fully initialized?
Your assumption is wrong, dereferencing iterator that points outside of container will lead to UB.
7 Most of the library’s algorithmic templates that operate on data structures have interfaces that use ranges. A range is a pair of iterators that designate the beginning and end of the computation. A range [i,i) is an empty range; in general, a range [i,j) refers to the elements in the data structure starting with the element pointed to by i and up to but not including the element pointed to by j. Range [i,j) is valid if and only if j is reachable from i. The result of the application of functions in the library to invalid ranges is undefined.
Upvotes: 3