0

Effective Modern C++的第 41 条中,以下情况之一是使 emplacement 函数有机会比插入 conterparts 性能更高的情况之一:

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

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

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

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

此外,作者补充说

此类节点是为就位功能创建的,而不是为插入功能创建的。

如果该对象已经在容器中,为什么我应该使用哪个函数将对象插入容器中?

4

1 回答 1

0

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

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

现在让我们看看在放置和插入时会发生什么:

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

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

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

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

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

因此,如果您的容器可能会拒绝该对象,您将有不必要的堆分配给Node. 这可能比从insert. 此外,您会发现它对emplace已经存在的对象是不合理的,因为它只会添加Node-allocations 1如果它失败并且没有奖励。


1:一个人可能有另一个超载emplace(value_type)不这样做。我不确定标准库是否这样做。

于 2021-01-09T20:34:47.280 回答