如果您使用带有值类型的C ++ std :: map(和其他容器),您会注意到插入到地图中会调用元素类型的析构函数。这是因为C ++规范要求operator []的实现与等效:
(*((std::map<>::insert(std::make_pair(x, T()))).first)).second
它会调用您的类型的默认构造函数来构建该对。然后将该临时值复制到地图中,然后进行破坏。可以在this stackoverflow post和here on codeguru中找到对此项的确认。
我觉得奇怪的是,这可以在不需要临时变量的情况下实现,但仍然是等效的。 C ++的一个特性叫做"inplace new"。 std :: map和其他容器可以为对象分配空白空间,然后在分配的空间上显式调用元素的默认构造函数。
我的问题:为什么我所见过的std :: map的所有实现都没有使用inplace来优化此操作?在我看来,它将大大提高这种低级别操作的性能。但是很多人都研究过STL代码库,所以我认为必须有这样的原因。
答案 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
,这需要调用复制构造函数。
不错的想法。