【问题标题】:std::unordered_map::operator[] - why there are two signatures?std::unordered_map::operator[] - 为什么有两个签名?
【发布时间】:2015-07-02 14:10:25
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

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

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

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

    至于第二个问题,我可能错了,但它应该将第二个重载视为最佳重载。

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

    【讨论】:

    • 我不确定它是否是为了优化,您可能会错过某些类型是可移动但不可复制的。在这种情况下,如果没有此重载,您将无法将此类类型用作键。
    • @Slava 这是一个非常有效的观点,我什至会将它包含在我的答案中,但在实践中我看不到为什么您希望将仅移动类型作为地图的键类型。您不会将互斥体、线程或唯一指针作为键。您应该只使用行为类似于值类型的类型
    【解决方案2】:

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

    因此,您可以避免对象/键的额外副本。您可以在以下示例中看到这一点:

    #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; 的情况下可以看到,与调用复制构造函数的语句fmap[a] = 100; 相比,右值对象被移动。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-11
      • 1970-01-01
      • 2015-12-16
      • 2010-11-09
      • 2015-03-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多