【问题标题】:Using The [] Operator Efficiently With C++ unordered_map在 C++ unordered_map 中有效地使用 [] 运算符
【发布时间】:2011-08-01 11:27:36
【问题描述】:

首先有人可以澄清一下在 C++ 中使用 [] 运算符和 unordered_map 进行查找是否包含对 find() 方法的调用,或者使用 [] 运算符是否比 find() 更快?

其次,在下面的代码中,我怀疑在键不在 unordered_map 中的情况下,我正在通过 map[key] = value 行进行第二次查找,以便替换使用创建的默认值当键不存在时的 [] 运算符。

这是真的吗,如果是这样的话,有没有办法(可能通过使用指针或其他东西)我可能在任何情况下都只执行一次查找(可能通过存储放置值/读取值的地址from) 并且仍然实现相同的功能?如果是这样,显然这将是一个有用的效率改进。

这里是修改后的代码摘录:

    int stored_val = map[key]; // first look up. Does this wrap ->find()??

    // return the corresponding value if we find the key in the map - ie != 0
    if (stored_val) return stored_val;

    // if not in map
    map[key] = value; 
       /* second (unnecessary?) look up here to find position for newly 
          added key entry */

   return value;

【问题讨论】:

    标签: c++ unordered-map


    【解决方案1】:

    operator[] 将为您插入一个具有默认构造值的条目(如果尚不存在)。它等效于,但可能会比以下更有效地实现:

    iterator iter = map.find(key);
    
    if(iter == map.end())
    {
        iter = map.insert(value_type(key, int())).first;
    }
    
    return *iter;
    

    operator[] 可以比使用 find()insert() 手动执行工作更快,因为它可以省去重新散列密钥的麻烦。

    您可以解决在代码中进行多次查找的一种方法是引用该值:

    int &stored_val = map[key];
    
    // return the corresponding value if we find the key in the map - ie != 0
    if (stored_val) return stored_val;
    
    // if not in map
    stored_val = value;
    
    return value;
    

    请注意,如果映射中不存在该值,operator[] 将默认构造并插入一个。因此,虽然这将避免多次查找,但如果使用默认构造 + 分配比复制或移动构造更慢的类型,它实际上可能会更慢。

    虽然int 默认构造为 0,但您可以将 0 视为一个表示空的幻数。您的示例中可能就是这种情况。

    如果你没有这样的幻数,你有两个选择。您应该使用什么取决于您计算该值的成本。

    首先,当散列密钥很便宜但计算值很昂贵时,find() 可能是最佳选择。这将散列两次,但仅在需要时计算值:

    iterator iter = map.find(key);
    
    // return the corresponding value if we find the key in the map
    if(iter != map.end()) return *iter;
    
    // if not in map
    map.insert(value_type(key, value));
    
    return value;
    

    但如果你已经获得了价值,你可以非常有效地做到这一点——也许比使用上面的引用 + 幻数更有效:

    pair<iterator,bool> iter = map.insert(value_type(key, value));
    return *iter.first;
    

    如果map.insert(value_type) 返回的布尔值为真,则该项目已插入。否则,它已经存在并且没有进行任何修改。返回的迭代器指向映射中插入的或现有的值。对于您的简单示例,这可能是最佳选择。

    【讨论】:

    • +1:引用的使用是我平时做的,可读性强,简洁高效
    • 理论上我认为可能存在这样的方法,所以我非常感谢您向我展示如何做到这一点并增加您的专业知识。考虑到我在执行过程中使用了这个功能几十万次,这应该会为我节省大量的执行时间。非常感谢
    • 另外,关于您的编辑:0 在我的程序上下文中是一个有意义的数字,但是当我存储第一个值时,我会记下它的键(使用静态全局变量)并在随后的调用中如果 stored_val 等于 0,我将进行密钥检查,以查看当前密钥是否等于映射到值 0 的密钥。这解决了问题,但感谢您的有效关注。
    • operator[] 也可能比find() 加上insert() 慢,因为它必须默认构造对象,然后分配给它,其中find() 加上insert() 构造(可能C++03 中的一个附加复制结构)。这是否比重新散列更贵或更便宜(或者实现是否必须重新计算它刚刚查找的值的散列)将取决于。你无法真正提前知道。
    • 在地图中我使用的是原始类型,所以这是否避免了您提到的对象的构造,我想在使用引用类型的地图中会发生这种情况?
    【解决方案2】:

    您可以检查一个元素是否存在,如果它不存在则插入一个新元素,使用特殊的insert 函数返回一个pair&lt;iterator, bool&gt;,其中布尔值告诉你如果该值已被实际插入。比如代码here

      unordered_map<char, int> mymap;
      pair<unordered_map<char,int>::iterator,bool> ret;
    
      // first insert function version (single parameter):;
      mymap.insert ( pair<char,int>('z',200) );
      ret=mymap.insert (pair<char,int>('z',500) ); 
      if (ret.second==false)
      {
        cout << "element 'z' already existed";
        cout << " with a value of " << ret.first->second << endl;
      }
    

    此处的代码将&lt;'z',200&gt; 对插入到映射中(如果它不存在)。如果返回的对的第二个元素的值为真,则返回插入的迭代器;如果对的第二个元素为假,则返回元素实际所在的迭代器。

    【讨论】:

    • 这是一个有用的答案,非常感谢您的意见。我想我会选择其他一个使用存储引用的方法,因为它在可读性方面看起来更清晰(我不确定效率的差异!),但还是谢谢你们。非常感谢您的帮助。
    • 在计算值很昂贵的(通常)情况下,始终无法插入。在这种情况下,映射用于避免重新计算值 - 这是您的代码完全缺乏的优势。
    • @Sjoerd,根据问题,计算值似乎不是这个过程的关键耗时任务。如果不是,为什么要尝试优化对(哈希)映射的一两次访问,这主要是 O(1)?
    • @Diego 好点,虽然计算哈希也可能很耗时。
    • @Diego 请注意,耗时的计算也是 O(1),因为它很可能不依赖于 unordered_map 的大小。所以总是重新计算是 O(1) - 即使重新计算需要很长时间。
    【解决方案3】:

    首先有人可以澄清一下在 C++ 中使用 [] 运算符和 unordered_map 进行查找是否包含对 Find() 方法的调用,或者使用 [] 运算符是否比 Find() 更快?

    对此没有规定。 [] 的实现可以使用find(),它可以自行执行查找,也可以将查找委托给find() 在内部使用的某个私有方法。

    也不能保证哪个更快。 find() 涉及构造和返回迭代器的开销,而 [] 如果键不存在可能会更慢,因为在这种情况下它会插入一个新值。

    (...) 有没有一种方法(可能通过使用指针或其他方式)我可能在任何情况下都只执行一次查找 (...)

    如果键不在映射中,[] 将插入一个新的默认构造值,并返回一个引用。因此,您可以存储该引用以保存第二次查找:

    int& stored_val = map[key];  // Note the reference
    
    if (stored_val) return stored_val;
    
    // Use the reference to save a second lookup.
    stored_val = value; 
    
    return value;
    

    【讨论】:

    • 这看起来正是我所追求的,非常感谢。只是出于兴趣,如果 & 是“地址”,那么为什么说 *stored_val = value; 是不正确的?其中 * 表示“存储的值”?请纠正我对此处语法的可能误解!
    • &amp; 在 C++ 中用于“地址”(获取指针)和引用(隐式指针)。这里,int&amp; 是一个引用,而不是一个指针(这将是int*)。您不必取消引用引用,因此无需编写*stored_value = ...
    猜你喜欢
    • 2020-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-26
    • 2016-01-27
    • 1970-01-01
    • 2017-03-23
    • 2017-07-28
    相关资源
    最近更新 更多