I understand that some containers in C++, such as map
or unordered_map
, have a mechanism that allows accessing values directly if you have the key.
I'm not sure if this is entirely correct...
The point here is that I want to use the contains method to check that the element exists and then directly retrieve the value with map[key]
or map.at[key]
.
I read somewhere random that doing this is bad practice because it would be performing two searches instead of just one...
So I had the question... does actually having the key serve any purpose? Will access not be immediate? Does find
iterate over all elements until it finds the key or value?
What is better?
example contains
if (storageMap.contains(key)) {
return storageMap.at(key);
}
Vs
example find
auto it = storageMap.find(key);
if (it != storageMap.end()) {
return it.second;
}
If you could explain to me why one is better than the other, it would be perfect. Alternatively, if both methods are equivalent, explaining why would also be useful. It would also be helpful to know if the compiler will make example contains
are efficient as example find
I would say that method 2. with find
is more preferable in your situation. In your snippet, you are accessing the element right away.
In the case of std::map:
contains
+ at
will perform two BST tree traversals 2 x O(log N)
operations. Internally 1x find
+ 1x lower_bound
find
will perform one BST traversal 1 x O(log N) + O(1)
one iterator access operation. Internally just find
.However, if you do not need to access the found element at all, but rather want to return a boolean existence flag, contains
would make more sense. In this case, the performance of find
and contains
will be identical with contains
being more expressive and easier to read.
Here is the GCC implementation of contains
. You can see that it's just a wrapper over find
:
#if __cplusplus > 201703L
///@{
/**
* @brief Finds whether an element with the given key exists.
* @param __x Key of (key, value) pairs to be located.
* @return True if there is an element with the specified key.
*/
bool
contains(const key_type& __x) const
{ return _M_t.find(__x) != _M_t.end(); }
template<typename _Kt>
auto
contains(const _Kt& __x) const
-> decltype(_M_t._M_find_tr(__x), void(), true)
{ return _M_t._M_find_tr(__x) != _M_t.end(); }
///@}
#endif
Implementation of at
is using the lower_bound
:
/**
* @brief Access to %map data.
* @param __k The key for which data should be retrieved.
* @return A reference to the data whose key is equivalent to @a __k, if
* such a data is present in the %map.
* @throw std::out_of_range If no such data is present.
*/
mapped_type&
at(const key_type& __k)
{
iterator __i = lower_bound(__k);
if (__i == end() || key_comp()(__k, (*__i).first))
__throw_out_of_range(__N("map::at"));
return (*__i).second;
}