53

I have read different articles on web and questions at stackoverflow, but for me it is not clear is there any exclusive case when it is better to use std::map::at to retrieve map element.

According to definition, std::map::at

Returns a reference to the mapped value of the element identified with key k.

If k does not match the key of any element in the container, the function throws an out_of_range exception.

For me only case when it is worth to use std::map::at when you 100% sure that element with particular key exist, otherwise you should consider exception handling.

  1. Is there any case where std::map::at considered as most efficient and elegant way to do? In what cases you will recommend to use std::map::at ?
  2. Am I right that it is better to use map::find() when there is a possibility to not have element with such a key? And map::find() it is faster and more elegant approach?
if ( map.find("key") != map.end() )
{
    // found 

} else
{
    // not found
}

p.s

map::operator[] sometimes can be dangerous, because if an element doesn't exist then it will inserts it.

EDITED: links somehow related link 1 link 2 link 3 link 4 link 5 link 6

Community
  • 1
  • 1
T M
  • 2,905
  • 1
  • 24
  • 48
  • 1
    A (maybe obvious) side note: When you store the iterator that is returned by `map.find("key")`, then you can use the `it.second` to access the value, so then there is no need to use `at()` or `[]` at all. – Marco13 Oct 20 '15 at 16:24
  • 2
    Note that `std::map::at()` was introduced in C++11, and using `std::map::find()` was previously the only option. Well, that and `std::map::count()`. – JPhi1618 Oct 20 '15 at 16:28
  • One use case where I am forced to use `std::map::at()` is when the `valueType` is not default constructible, which is a requirement by `std::map::operator[]` – talekeDskobeDa Aug 04 '20 at 11:41
  • @T M, you have mentioned, "... only case when it is worth to use `std::map::at` when you 100% sure that element with particular key exists ...". However, no offense, but should it be "... only case when it is worth to use `std::map::at` when you ***are NOT*** 100% sure that element with particular key exists ...". Because, if you are 100% sure, why wouldn't you use the `[]` operators? right? Please correct me if I'm mistaken. Thank you! – Milan Apr 29 '21 at 18:51
  • @talekeDskobeDa If it does not default constructible then how would you use it in `std::map::at()`? If possible, could you please give a small example? It would help me a lot in understanding your point better. Thank you! – Milan Apr 29 '21 at 18:54

8 Answers8

81

Contrary to most existing answers here, note that there are actually 4 methods related to finding an element in a map (ignoring lower_bound, upper_bound and equal_range, which are less precise):

  • operator[] only exist in non-const version, as noted it will create the element if it does not exist
  • at(), introduced in C++11, returns a reference to the element if it exists and throws an exception otherwise
  • find() returns an iterator to the element if it exists or an iterator to map::end() if it does not
  • count() returns the number of such elements, in a map, this is 0 or 1

Now that the semantics are clear, let us review when to use which:

  • if you only wish to know whether an element is present in the map (or not), then use count().
  • if you wish to access the element, and it shall be in the map, then use at().
  • if you wish to access the element, and do not know whether it is in the map or not, then use find(); do not forget to check that the resulting iterator is not equal to the result of end().
  • finally, if you wish to access the element if it exists or create it (and access it) if it does not, use operator[]; if you do not wish to call the type default constructor to create it, then use either insert or emplace appropriately
Matthieu M.
  • 251,718
  • 39
  • 369
  • 642
  • Hi Matthieu, can you please also comment on the performance impact between map.find() == map.end() following by operator[] and catching the exception. – Anirudh May 21 '19 at 07:38
  • @Anirudh: There's no exception with either of `find` or `operator[]`. Could you be more specific about what you have in mind? – Matthieu M. May 21 '19 at 07:45
  • 1
    So one way to go would be "if (map.find(key) != map.end()) { x= map[key] }" and the other way would be "try { x = map.at(key)} except(int e) { /* Some logic error processing */ }" . Apart from the logical flow consideration, if there any performance difference between the two ? – Anirudh May 22 '19 at 03:03
  • 1
    @Anirudh: Both are bad. The first (find+[]) is doing two look-ups, the second is using exception as control-flow. You should just use `find` in this circumstance: `if (auto it = map.find(key); it != map.end()) { x = it->second; }` – Matthieu M. May 22 '19 at 06:37
  • Why not use ``map.find(key) == map.end()`` or ``map.find(key) != map.end()`` to find out if a key exists or not (no access)? Isn't it more self-explanatory? What are the performance comparison with this approach vs using ``count``? – Robur_131 May 25 '20 at 10:26
  • @Robur_131: Since `count` will return 0 or 1 (on a `map`, not on a `multimap`) performance should be identical. The main reason I recommend `count` is that's it easier to use: shorter, no risk of mistakenly comparing to the `.end()` of another map, etc... – Matthieu M. May 25 '20 at 10:29
  • @MatthieuM. if possible, could you please elaborate on this *"... if you do not wish to call the type default constructor to create it, then use either `insert` or `emplace` appropriately ..."*? or share any relevant resources/links? Thank you! – Milan Apr 29 '21 at 18:57
  • @Milan: This is a reminder that `map[key] = value;` is an anti-pattern. `map[key]` will create the default value of element if absent which (a) requires a default constructor and (b) is inefficient if immediately overridden. By contrast, `map.insert(std::make_pair(key, value))` will not call the default constructor. – Matthieu M. Apr 30 '21 at 12:16
21

std::map::at() throws an out_of_range exception if the element could not be found. This exception is a kind of logic_error exception which for me is a kind of synonym of assert() from the usage standpoint: it should be used to report errors in the internal logic of the program, like violation of logical preconditions or class invariants.

Also, you can use at() to access const maps.

So, for your questions:

  1. I will recommend using at() instead of [] when accessing const maps and when element absence is a logic error.
  2. Yes, it's better to use map::find() when you're not sure element is here: in this case it's not a logic error and so throwing and catching std::logic_error exception will not be very elegant way of programming, even if we don't think about performance.
emlai
  • 37,861
  • 9
  • 87
  • 140
alexeykuzmin0
  • 6,008
  • 1
  • 24
  • 49
13

As you noted, there are three different ways to access elements in a map: at(), operator[] and find() (there are also upper_bound, lower_bound and equal_range, but those are for more complicated circumstances where you might want to find a next/previous element etc.)

So, when should you use which one?

operator[] is basically "if it does not exist, create one with a default-constructed mapped element". That means it won't throw (except in the corner cases when the memory allocation throws or one of the key or value constructors throw), and you definitely get a reference to the element you looked for - either the existing one or the newly created.

at() throws if there is no element for that key. Since you should not use exceptions for normal program flow, using at() is saying "I am sure there is such an element." But with the added benefit that you get an exception (and not undefined behavior) if you are wrong. Don't use this if you are not positive that the element exists.

find() says "there may or may not be such an element, let's see..." and offers you the possibility to react to both cases differently. It therefore is the more general approach.

Arne Mertz
  • 22,733
  • 2
  • 43
  • 86
  • 3
    Great answer; I just wanted to clarify the meaning of the assertion _"I am sure there is such an element."_ - by using `at()` that way, you get notified with an exception **if there's a bug in your code**. So `at()` is a great way to not unintentionally hide bugs. – Johann Gerell Oct 20 '15 at 12:23
  • But you can use map::find and put assertion if you do not find element. Correct me if I am wrong but assertions executing in debug and excluded from release build. So in some cases it is better to put assertion rather than handle exception. And again we have situation when it depends from your case :) and it is great that language gives us a lot of options we can choose :) if you need exception than use map::at otherwise you can use map:: find or operator[] – T M Oct 20 '15 at 12:35
  • 1
    @TM In production code, where the assertion is not compiled in, what do you expect to happen if you continue from `find()` assuming element was found? Just continue on and let the program crash and burn? Or add `if` statement to handle the exceptional case elegantly? Neither seems preferable to having `at` throw an exception in any case I can think of. – hyde Oct 20 '15 at 12:56
  • @hyde 28 I am not saying that assertions replace exceptions ( everyone can google assertion vs exception and read bunch of articles :) ) I am just mean that in some cases you need assertion and at that case you can use map::find() – T M Oct 20 '15 at 13:26
6

All 3 of find, operator[] and at are useful.

  • find is good if you don't want to accidentally insert elements, but merely act if they exist.

  • at is good if you expect that something should be on a map and you'd throw an exception if it wasn't anyway. It can also access const maps in a more concise matter than find (where you can't use op[])

  • op[] is good if you want to insert a default element, such as for the word counting program which puts an int 0 for every word encountered for the first time (with the idiom words[word]++;).

Bartek Banachewicz
  • 36,624
  • 6
  • 81
  • 129
3

This depends on what the requirements are for this function and how you are structuring the project. If you are supposed to return an object and you can't because it was not found then it leaves you with two options on how to handle that. You could through an exception or you could return some sort of sentinel that means nothing was found. If you want to throw an exception then use at() as the exception will be thrown for you. If you do not want to throw an exception then use find() so you do not have to deal with handling an exception just to return a sentinel object.

NathanOliver
  • 150,499
  • 26
  • 240
  • 331
3

I think, it depends on your usecase. The return type of std::map::at() is an lvalue reference to the value of the found element, while std::map::find() returns an iterator. You might prefer

return myMap.at("asdf"s) + 42;

in expressions over the more elaborate

return myMap.find("asdf"s)->second + 42;

Whenever you use the result of std::map::at() in an expression, you expect the element to exist, and regard a missing element as an error. So an exception is a good choice to handle that.

cdonat
  • 2,670
  • 13
  • 24
2

I guess the difference is semantics.

std::map::at() looks like this on my machine:

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;
}

As you can see, it uses lower_bound, then checks for end(), compares keys, and throws the exception where needed.

find() looks like this:

iterator
find(const key_type& __x)
{ return _M_t.find(__x); }

where _M_t is a red-black tree that stores the actual data. Obviously, both function have the same (logarithmic) complexity. When you use find() + check for end(), you are doing almost the same thing that at does. I would say the semantic difference is:

  • use at() when you need an element at a specific location, and you assume that it is there. In this case, the situation of the element missing from the desired place is exceptional, thus at() throws an exception.
  • use find() when you need to find the element in the map. In this case the situation when the element is not present is normal. Also note that find() returns an iterator which you may use for purposes other than simply obtaining it's value.
SingerOfTheFall
  • 27,171
  • 7
  • 60
  • 100
2

map::at() returns a l-value reference, and when you return by reference, you can use all its available benefits such as method chaining.

example:

  map<int,typ> table;
  table[98]=a;
  table[99]=b;

  table.at(98)=table.at(99);

operator[] also returns the mapped value by reference, but it may insert a value if searched for key is not found, in which case container size increases by one.

This requires you to be extra cautious since you have to take care of iterator invalidation.

Am I right that it is better to use map::find() when there is a possibility to not have element with such a key? And map::find() it is faster and more elegant approach?

Yes, semantically it makes sense to use find() when you are not sure of the existence of element.Makes the code easier to understand even for a newbie.

As for the time efficiency, map is generally implemented as a RB-tree/some balanced binary search tree and hence, complexity is O(logN) for find().

C++ Spec:

T& operator[](const key_type& x);
Effects: If there is no key equivalent to x in the map, inserts value_type(x, T()) into the map. Requires: key_type shall be CopyInsertable and mapped_type shall be DefaultInsertable into *this. Returns: A reference to the mapped_type corresponding to x in *this. 4 Complexity: Logarithmic.

T& at(const key_type& x);
const T& at(const key_type& x) const; Returns: A reference to the mapped_type corresponding to x in *this. Throws: An exception object of type out_of_range if no such element present. Complexity: Logarithmic.

Community
  • 1
  • 1
basav
  • 1,355
  • 10
  • 19