为什么C++ std::map::operator[] 不使用就地新?

Why doesn't C++ std::map::operator[] use inplace new?

本文关键字:std C++ map operator 为什么      更新时间:2023-10-16

如果您将C++std::map(和其他容器)与值类型一起使用,您会注意到插入到map中会调用元素类型的析构函数。这是因为C++规范要求运算符[]的实现等效于此:

(*((std::map<>::insert(std::make_pair(x, T()))).first)).second

它调用类型的默认构造函数来构建该对。然后将该临时值复制到映射中,然后进行销毁。这一点的确认可以在这篇stackoverflow帖子和这里的codeguru上找到。

我觉得奇怪的是,这可以在不需要临时变量的情况下实现,并且仍然是等效的。C++有一个特性叫做"就地新"。std::map和其他容器可以为对象分配空白空间,然后在分配的空间上显式调用元素的默认构造函数。

我的问题是:为什么我看到的std::map的实现都没有使用inplace new来优化此操作?在我看来,这将大大提高这种低级别操作的性能。但是很多人都研究过STL代码库,所以我认为这样做一定有原因。

通常,根据较低级别的操作来指定像[]这样的较高级别操作是个好主意。

在C++11之前,如果不使用insert,使用[]将很难做到这一点。

在C++11中,为std::pair添加std::map<?>::emplace和类似的东西使我们能够避免这个问题。如果你重新定义了它,使用这样的就地构造,那么额外的(希望被忽略的)对象创建就会消失。

我想不出有什么理由不这样做。我鼓励你提出标准化。

为了演示无拷贝插入std::map,我们可以执行以下操作:

#include <map>
#include <iostream>
struct no_copy_type {
  no_copy_type(no_copy_type const&)=delete;
  no_copy_type(double) {}
  ~no_copy_type() { std::cout << "destroyedn"; }
};
int main() {
  std::map< int, no_copy_type > m;
  m.emplace(
    std::piecewise_construct,
    std::forward_as_tuple(1),
    std::forward_as_tuple(3.14)
  );
  std::cout << "destroy happens next:n";
}

实际示例--正如您所看到的,没有生成临时的。

所以如果我们更换

(*((std::map<>::insert(std::make_pair(x, T()))).first)).second

带有

(*
  (
    (
      std::map<>::emplace(
        std::piecewise_construct,
        std::forward_as_tuple(std::forward<X>(x)),
        std::forward_as_tuple()
    )
  ).first
).second

不会创建临时的(添加空白,这样我就可以跟踪()s)。

首先,如果未找到请求的<key>,则std::mapoperator[<key>]仅等效于插入操作。在这种情况下,只需要对密钥的引用,并且只需要对生成的存储值的引用。

其次,当插入新元素时,无法知道是否会执行复制操作。您可能有map[_k] = _v;,也可能有_v = map[_k];。当然,后者的要求与作业外的要求相同,即map[_k].method_call();,但使用复制构造函数(没有可从中构造的源)。关于插入,上述所有操作都要求调用value_type的默认构造函数并为其分配空间。即使我们在编写operator[]时可以知道我们处于赋值用例中,但由于操作顺序的原因,我们不能使用"inplace new"。必须首先调用value_type构造函数,然后调用value_type::operator=,这就需要调用复制构造函数。

不过想法不错。