std :: unordered_map :: emplace对象创建

时间:2014-05-16 21:19:17

标签: c++ c++11 unordered-map emplace

我正在选择将两种方法放入unordered_map中的一种方法之一:

std::unordered_map<Key, Value> map;
map.emplace(
  std::piecewise_construct,
  std::forward_as_tuple(a),
  std::forward_as_tuple(b, c, d));

VS

std::unordered_map<Key, DifferentValue> map;
auto& value = map[a];
if (value.isDefaultInitialized())
  value = DifferentValue(b, c, d);

我做了一些实验,看看哪一个能更好地发现插入独特元素时,行为(如效率)基本相同。

但是,在插入重复项目的情况下,并且认为Value或DifferentValue的构造不是微不足道的,我很惊讶地发现,无论是否插入对象,emplace都会构造对象。

所以,第二种方法似乎在这种情况下获胜,因为默认构造函数在那里只有isDefaultInitialized_(true)而且没有更多。

对于emplace,代码似乎是:

... _M_emplace(std::true_type, _Args&&... __args) {
  __node_type* __node = _M_allocate_node(std::forward<_Args>(__args)...);
  const key_type& __k = this->_M_extract()(__node->_M_v);
  ...
  if (__node_type* __p = _M_find_node(__bkt, __k, __code)) {
     _M_deallocate_node(__node);
     return std::make_pair(iterator(__p), false);
  }
  return std::make_pair(_M_insert_unique_node(__bkt, __code, __node), true);
}

所以,虽然我将使用第二种方法(即使它需要移动赋值和移动构造函数和额外字段),但我想知道为什么emplace创建一个后来忽略的对象有一个很好的理由?也就是说,它应该首先检查是否需要创建对象,如果它已经存在则提前出现?

(请注意,对于我的特定情况,默认初始化项目不被视为有效,因此问题实际上只是关于安抚)

为了记录,我在23.2.4表102下找到了一些东西:

Effects: Inserts a value_type object t constructed with std::forward<Args>(args)...
if and only if there is no element in the container with key equivalent to the
key of t.

我认为不允许创建对象。

2 个答案:

答案 0 :(得分:2)

在我看来,标准中的引用部分具有误导性,因为它表明,只有在容器中没有匹配元素时才构造对象。我猜他们试图说明:

  

效果:使用value_type构建t对象std::forward<Args>(args)...。当且仅当容器中没有与t的键相同的键的元素时才插入构造的对象t

原因是:函数emplace的实现必须构造t以便查明是否存在具有等效键的元素,因为实现必须调用哈希函数和等于谓词。但是,通常只能使用value_type类型的对象调用它们,而不能使用元组来构造这些对象。

理论上,可以指定 emplace 函数,如果已经存在具有等效键的元素,则不会构造t。有趣的是,类似的东西将添加到std::map::find的C ++ 14中。请参阅以下文档:

只要 compare 函数满足一些额外要求,就有两个可以与任意类型一起使用的重载。有趣的是,std::unordered_map没有这种过载。

答案 1 :(得分:0)

是的,std :: unordered_map :: emplace()所做的第一件事是在搜索之前(如果已经具有刚刚构造的KEY的元素中)在内存中创建要放置的KEY-VALUE对。存在于表格中。如果找到了这样的元素,则emplace()继续通过立即销毁新创建的元素来继续。这通常不是,为什么人们首先要使用emplace(),因为这是为了避免不必要的对象创建!

std::( unordered_)map :: emplace()的(IMHO)坏设计背后的原因可能是,首先创建KEY然后检查KEY是否存在的实现需要能够移动或将该密钥复制到KEY-VALUE对中的最终目的地(如果找不到该KEY)。由于将emplace()添加到STL容器中是专门针对不可复制的不可移动对象,因此依赖于可移动/可复制KEY的emplace实现将是不完整的。

但是,所有合理KEY的99%是可复制构造的或可移动构造的,或两者都是,因此应将它们与VALUE分开对待,而VALUE的构造可能要复杂得多。在C ++ 17(又名C ++ 1z)中,该语言的神灵对我们而言意义非凡,并添加了try_emplace()方法:其参数是对已经构造的KEY的引用,而参数仅是构造相应的KEY值VALUE。 try_emplace()首先搜索KEY。仅当密钥是新密钥时,才通过复制或移动密钥并就地构建值来构造新的密钥对。哇!