在 C++ unordered_map 中有效地使用 [] 运算符

Posted

技术标签:

【中文标题】在 C++ unordered_map 中有效地使用 [] 运算符【英文标题】:Using The [] Operator Efficiently With 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;

【问题讨论】:

【参考方案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 = ...

以上是关于在 C++ unordered_map 中有效地使用 [] 运算符的主要内容,如果未能解决你的问题,请参考以下文章

如何(有效地)以地图为值插入地图?

如何在 C++ 中单独锁定 unordered_map 元素

C++ 如何清空unordered_map

C++ - unordered_map 运算符 [],意外行为

C++ std::unordered_map 中使用的默认哈希函数是啥?

C++ 错误:“unordered_map”未命名类型