为什么不使用C ++ std :: map :: operator [] inplace new?

时间:2014-09-24 15:42:23

标签: c++ stl new-operator

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

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

它会调用您的类型的默认构造函数来构建该对。然后将该临时值复制到地图中,然后进行破坏。可以在this stackoverflow posthere on codeguru中找到对此项的确认。

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

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

2 个答案:

答案 0 :(得分:4)

一般来说,你指定一个更高级别的操作,比如[]更低级别的操作是个好主意。

在使用C ++ 11之前,如果不使用[],那么使用insert执行此操作会很困难。

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

我想不出不这样做的理由。我鼓励你提出标准化建议。

为了演示无副本插入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 << "destroyed\n"; }
};
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";
}

live example - 如您所见,没有生成临时值。

所以如果我们替换

(*((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

不会创建临时(添加空格以便我可以跟踪())。

答案 1 :(得分:0)

首先,operator[<key>]的{​​{1}}仅相当于插入操作,如果找不到请求的std::map。在这种情况下,只需要对密钥的引用,并且只引用存储的值。

其次,当插入新元素时,无法知道是否会出现复制操作。您可能拥有<key>,或者您可能拥有map[_k] = _v;。后者当然具有与赋值之外相同的要求,即_v = map[_k];,但使用复制构造函数(theres的无源构造)。关于插入,所有上述内容都要求调用map[_k].method_call();的默认构造函数并为其分配空间。即使我们知道何时写value_type我们在分配用例中,由于操作的顺序,我们也不能使用“inplace new”。必须首先调用operator[]构造函数,然后调用value_type,这需要调用复制构造函数。

不错的想法。