问题描述
在 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>;
重载来不这样做。我不确定标准库是否会这样做。