小编典典

在 C++ 地图中插入 vs emplace vs operator[]

all

我第一次使用地图,我意识到有很多方法可以插入元素。您可以使用emplace(), operator[]or insert(),
以及使用value_typeor之类的变体make_pair。虽然有很多关于所有这些的信息和关于特定案例的问题,但我仍然无法理解大局。所以,我的两个问题是:

  1. 他们每个人比其他人有什么优势?

  2. 是否需要在标准中添加 emplace?没有它,有什么是不可能的吗?


阅读 67

收藏
2022-05-24

共1个答案

小编典典

在地图的特定情况下,旧选项只有两个:operator[]insert(不同风格的insert)。所以我将开始解释这些。

operator[]是一个 查找或添加
运算符。它将尝试在地图中查找具有给定键的元素,如果存在,它将返回对存储值的引用。如果没有,它将使用默认初始化创建一个插入到位的新元素并返回对它的引用。

insert函数(在单元素风格中)接受一个value_typestd::pair<const Key,Value>),它使用键(first成员)并尝试插入它。因为std::map如果存在现有元素则不允许重复,因此它不会插入任何内容。

两者的第一个区别是operator[]需要能够构造一个默认的初始化
,因此不能用于无法默认初始化的值类型。两者之间的第二个区别是当已经存在具有给定键的元素时会发生什么。该insert函数不会修改映射的状态,而是返回一个迭代器到该元素(和一个false指示它没有被插入)。

// assume m is std::map<int,int> already has an element with key 5 and value 0
m[5] = 10;                      // postcondition: m[5] == 10
m.insert(std::make_pair(5,15)); // m[5] is still 10

在参数的情况下insert是一个对象value_type,它可以以不同的方式创建。您可以使用适当的类型直接构造它或传递value_type可以构造的任何对象,这就是std::make_pair发挥作用的地方,因为它允许简单地创建std::pair对象,尽管它可能不是您想要的......

以下调用的净效果是 相似 的:

K t; V u;
std::map<K,V> m;           // std::map<K,V>::value_type is std::pair<const K,V>

m.insert( std::pair<const K,V>(t,u) );      // 1
m.insert( std::map<K,V>::value_type(t,u) ); // 2
m.insert( std::make_pair(t,u) );            // 3

但实际上并不相同...... [1] 和 [2] 实际上是等价的。在这两种情况下,代码都会创建一个相同类型的临时对象 ( std::pair<const K,V>)
并将其传递给insert函数。该insert函数将在二叉搜索树中创建适当的节点,然后将value_type参数中的部分复制到节点。使用的好处value_type是,value_type总是
匹配 value_type,你不能错误输入std::pair参数的类型!

区别在[3]。该函数std::make_pair是一个模板函数,它将创建一个std::pair. 签名是:

template <typename T, typename U>
std::pair<T,U> make_pair(T const & t, U const & u );

我故意不向
提供模板参数std::make_pair,因为这是常见的用法。这意味着模板参数是从调用中推导出来的,在这种情况下是T==K,U==V,所以调用std::make_pair将返回
a std::pair<K,V>(注意缺少const)。签名要求value_type接近
与调用返回的值不同std::make_pair。因为它足够接近,它将创建一个正确类型的临时文件并复制初始化它。这将反过来被复制到节点,总共创建两个副本。

这可以通过提供模板参数来解决:

m.insert( std::make_pair<const K,V>(t,u) );  // 4

但这仍然容易出错,就像在 case [1] 中显式键入类型一样。

到目前为止,我们有不同的调用方式insert,需要在value_type外部创建并将该对象的副本复制到容器中。或者,operator[]如果类型是
默认可构造可分配 的(故意只关注m[k]=v),则可以使用它,并且它需要一个对象的默认初始化并将值 复制 到该对象中。

在 C++11 中,通过可变参数模板和完美转发,有一种新方法可以通过 放置
(就地创建)将元素添加到容器中。不同容器中的emplace函数基本上做同样的事情:该函数不是获取 复制到容器中的
,而是获取将转发给存储在容器中的对象的构造函数的参数。 __

m.emplace(t,u);               // 5

在 [5] 中,std::pair<const K, V>没有创建并传递给emplace,而是传递对tu对象的引用,然后emplace将它们转发给value_type数据结构内的子对象的构造函数。在这种情况下,
根本不会 复制std::pair<const K,V>,这是emplaceC++03
替代方案的优势。在这种情况下,insert它不会覆盖地图中的值。


一个我没有考虑过的有趣问题是如何emplace为地图实际实现,这在一般情况下不是一个简单的问题。

2022-05-24