6

我正在选择将事物放入 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));

对比

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

我做了一些实验,看看哪一个表现更好,发现在插入独特元素时,行为(如效率)基本上是等效的。

但是,在插入重复项的情况下,考虑到Value或DifferentialValue的构造并非易事,我惊讶地发现,无论插入与否,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 创建一个后来忽略的对象有很好的理由吗?也就是说,它是否应该首先检查它是否需要创建对象,如果它已经存在则提前退出?

(请注意,对于我的特殊情况,默认初始化项不被认为是有效的,所以问题实际上只是关于 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.

我认为这将允许不创建对象。

4

2 回答 2

4

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

效果:用构造一个value_type对象。当且仅当容器中不存在具有与 的键等效的键的此类元素时,才插入构造的对象。tstd::forward<Args>(args)...tt

原因是:函数的实现emplace必须构造t以找出是否存在具有等效键的元素,因为实现必须调用散列函数和equals谓词。但是,通常只能使用 type 的对象调用它们value_type,而不是用于构造这些对象的元组。

理论上,可以指定一个emplacet函数,如果已经存在具有等效键的元素,则该函数不会构造。有趣的是,C++14 会为std::map::find. 请参阅以下文档:

有两个重载可以与任意类型一起使用,只要比较函数满足一些额外的要求。有趣的是,对于std::unordered_map.

于 2014-05-17T17:57:48.833 回答
1

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

std::(unordered_)map::emplace() 的(恕我直言)破坏设计背后的原因可能是,首先创建 KEY 然后检查 KEY 是否存在的实现需要能够移动或复制如果未找到 KEY,则将 KEY 指向其在 KEY-VALUE-pair 中的最终目的地。由于 emplace() 被添加到 STL 容器中,专门用于处理不可复制的不可移动对象,因此依赖于可移动/可复制 KEY 的 emplace 实现将是不完整的。

然而,所有合理的 KEY 中的 99% 要么是可复制构造的,要么是可移动构造的,或两者兼而有之,因此它们应该与 VALUE 分开处理,后者的构造可能要复杂得多。而对于 C++17 aka C++1z,语言之神对我们来说很好,并添加了 try_emplace() 方法:它的参数是对已经构造的 KEY 的引用,并且只需要构造相应的参数VALUE 到位。try_emplace() 首先搜索 KEY。只有,如果 KEY 是新的,则通过复制或移动 KEY 并在适当位置构造 VALUE 来构造新的 KEY-VALUE 对。欢呼!

于 2019-01-19T00:53:56.513 回答