std :: unordered_map :: operator [] - 为什么有两个签名?

时间:2015-07-02 14:10:25

标签: c++ c++11 unordered-map rvalue-reference

在C ++ 11中,有两个版本的 std :: unordered_map :: operator [] ,即:

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

有两个问题:

1)为什么第二个是必要的 - 第一个允许将常量传递给函数,因为第一个包含关键字 const

2)例如,在这种情况下将调用哪个版本1或2:

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

2 个答案:

答案 0 :(得分:6)

通常,密钥仅用于比较目的,因此您可能想知道为什么rvalue语义是必要的:const引用应该已经涵盖了这种情况。

但有一点需要注意的是,operator []确实可以创建一个新的键/值对:如果该键在地图中已经存在。

在这种情况下,如果使用了第二个重载,则映射可以安全地移动映射中提供的键值(默认初始化值)。在我看来,这是一个非常罕见且可以忽略不计的优化,但是当你成为C ++标准库时,你不应该花费任何努力来保存某个循环,即使它只发生一次!

关于第二个问题,我可能错了,但应该将第二次过载视为最佳过载。

编辑: 还有一个有效的观点,它可能允许您使用仅移动对象作为关键值,即使它是一个值得商榷的决定

答案 1 :(得分:1)

出于性能原因,它就在那里。例如,如果键是右值,则在插入新元素时移动键而不是复制键。

因此,您可以避免使用对象/密钥的额外副本。您可以在以下示例中看到:

#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

输出:

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

正如fmap[Foo()] = 1;中的情况所示,rvalue对象与调用复制构造函数的语句fmap[a] = 100;形成对比。