关于不允许重复的容器的放置和插入之间的不同效率的混淆

问题描述

Effective Modern C++ 的第 41 条中,以下情况是让定位函数有机会比插入函数更高效的情况之一:

容器不太可能将新值作为重复项拒绝

原因是,给定构造函数的参数,尝试将其插入容器的对象,emplace 函数必须构造该对象以评估它是否已经存在于容器,在这种情况下,建筑已经浪费了,其次也是不可避免的破坏浪费。

这里我已经有些怀疑了。如果我要使用插入函数来避免这种情况,那么我将自己构造对象(可能是一个临时对象,将传递给定位函数的参数传递给其构造函数),然后将其传递给插入函数,然后那个临时的,如果一个相等的值已经在容器中,就会被销毁。

所以我看不出有什么不同。

此外,作者补充说

与插入功能相比,此类节点更常用于定位功能

如果该对象已经在容器中,为什么我使用哪个函数在容器中插入对象会产生影响?

解决方法

我不确定这是否以及如何适用于无序容器。但是 std::set 通常在内部由二叉树(通常是红黑树)表示。您可以假设节点看起来有点像这样:

struct Node {
    Node* parent,left_child,right_child;
    value_type value;
}

现在让我们看看在 emplaceing 和 insert 时会发生什么:

  • emplace(args...):现在,我们需要一个 value_type 对象来进行比较。但是我们不会创建一个我们稍后会移动的临时文件(std::set 不要求 value_type 是可移动或可复制的!)。我们将直接在堆上创建一个 Node a 并从 value 就地构造 args。之后我们检查 value 是否已经在集合中。如果是这样,我们删除 a 并完成。否则,我们调整 Node*a 以将其纳入集合。

  • insert(val):在这里,我们已经有了一个对象(可能是临时对象)。所以我们找出它是否已经在集合中。如果是这样,什么都不会发生,我们就完成了。如果它不在集合中,我们现在分配一个 Node 并将 val 复制/移动到该 Node 并设置 Node* 以将其放入集合中。

现在让我们分析不同的场景。

  • emplace(args...):从 value_type 构造的 args... 对象是否已经在地图中并不重要。我们将始终分配一个 Node 并使用一次 value_type(args...) 构造函数。没有移动,没有副本。如果对象已经存在,我们将 delete 一个 Node 从而调用 value_type 的析构函数。
  • insert(val):如果 val 已经存在,我们在 insert 中根本不做任何构造。如果没有,我们分配一个 Node 并将 val 复制/移动到其中。如果您在将 val 传递给 args... 的同时从 insert 构造 insert(value_type(args...))(即调用 value_type(args...)),那么我们当然还有另一个 emplace 以及对这个临时的析构函数。

因此,Node 将始终分配 insert(value_type(args...)),无论该值是否存在。如果值存在,Node 将没有,但如果值不存在,它将有一个额外的移动。

因此,如果您的容器可能会拒绝该对象,您将为 insert 分配不必要的堆。这可能比从 emplace 额外移动的成本更高。此外,您将看到 Node 一个已经存在的对象永远是不合理的,因为如果它失败并且没有奖励,那只会添加 emplace(value_type)-allocations1


1:一个人可以有另一个 SELECT <non-pivoted column>,[first pivoted column] AS <column name>,[second pivoted column] AS <column name>,... [last pivoted column] AS <column name> FROM (<SELECT query that produces the data>) AS <alias for the source query> PIVOT ( <aggregation function>(<column being aggregated>) FOR [<column that contains the values that will become column headers>] IN ( [first pivoted column],[second pivoted column],... [last pivoted column]) ) AS <alias for the pivot table> <optional ORDER BY clause>; 重载来不这样做。我不确定标准库是否会这样做。