简体   繁体   中英

std::unordered_map::operator[] - why there are two signatures?

In the C++11, there are two versions of std::unordered_map::operator[] , namely:

mapped_type& operator[] ( const key_type& k ); //1
mapped_type& operator[] ( key_type&& k ); //2

There are two questions:

1) Why the second one is necessary - the first one allows to pass constant to the function, since the first one contains the keyword const

2) For example, which version, 1 or 2, will be called in this case:

std::unordered_map<std::string, int> testmap;
testmap["test"] = 1;

Normally, the key is only used for comparison purposes, so you might wonder why rvalue semantics are necessary: a const reference should already cover that case.

But one thing to note is that operator[] can indeed create a new key/value pair: if the key wasn't already existent in the map.

In that case, if the second overload was used, then the map can safely move the provided key value in the map (while default initializing the value). It's a pretty rare and negligible optimization in my opinion, but when you're the C++ standard library, you shouldn't spare any efforts to save someone a cycle, even if it happens just once!

As for the second question, I might be wrong but it should consider the second overload as the best overload.

Edit: There is also a valid point that it might allow you to use move-only objects as key values, even if it's a debatable decision

It's there for performance reasons. For example if the key is an rvalue, the key is moved instead of copied when a new element is inserted.

Thus, you avoid extra copy of an object/key. You can see this in the following example:

#include <iostream>
#include <unordered_map>

struct Foo {
  Foo() { std::cout << "Foo() called" << std::endl; }
  Foo(Foo const &other) { std::cout << "Foo(Foo const &other) called" << std::endl; }
  Foo(Foo &&other) { std::cout << "Foo(Foo &&other) called" << std::endl; }
  int i = 0;
};

bool operator==(Foo const &lhs, Foo const &rhs) {
  return lhs.i == rhs.i;
}

void hash_combine(std::size_t& seed, const Foo& v) {
    std::hash<int> hasher;
    seed ^= hasher(v.i) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}

struct CustomHash {
  std::size_t operator()(Foo const& v) const  {
    std::size_t res = 0;
    hash_combine(res, v);
    return res;
  }
};

int main() {
  std::unordered_map<Foo, int, CustomHash> fmap;

  Foo a;
  a.i = 100;
  fmap[a] = 100;
  fmap[Foo()] = 1;

}

LIVE DEMO

Output:

Foo() called
Foo(Foo const &other) called
Foo() called
Foo(Foo &&other) called

As can see in the case fmap[Foo()] = 1; the rvalue object is moved in contrast with statement fmap[a] = 100; where a copy constructor is called.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM