【问题标题】:best way to insert std::map [duplicate]插入 std::map 的最佳方法 [重复]
【发布时间】:2014-10-13 13:41:53
【问题描述】:

拥有:

std::map<const std::string,A > cache;

你将如何插入这个容器(重复尝试是可能的):

cache.insert(std::make_pair(id,ps));

cache.insert(std::pair<std::string,A>(id,ps));

if(cache.find(id) == cache.end()){
 cache[id] = ps;
}

为什么??(在时间和记忆方面)

你有更好的解决方案吗?

更新: 我没有使用 C++11

更新 2: 好的,到目前为止我们意识到:

make_pairpair&lt;&gt; 是类似的。

insert[ ](有或没有ifchecking)都将调用copy。所以..是之间的竞争:

  1. insert
  2. [ ](withifchecking)
  3. [ ](withifchecking 和swap)

你更喜欢哪一个?

再次感谢

【问题讨论】:

  • cache[id] = ps; 没有检查
  • @BryanChen 但这改变了语义
  • cache.emplace(id, ps) 如果你有 C++11。显然不是最后一个,因为它搜索了两次地图。当然也不是第二个,因为第一个是一种更简洁、更精简的编写方式。嗯,也许我应该写一个答案。
  • @BryanChen insert() 不会更改已经存在的元素。
  • cache.insert(std::pair<:string>(id,ps)) 不会转发值,它会复制它,因为 map 的 value_type 是 std::对。注意常量。

标签: c++ stl stdmap c++03


【解决方案1】:

我建议emplace:

template< class... Args >
std::pair<iterator,bool> emplace( Args&&... args );

// usage:
cache.emplace(id, ps);

但是,这有点取决于您是否想要第二次插入

  • fail(如上所示,返回 std::pair&lt;iterator,bool&gt; 其中.second 表示插入成功或失败 - 如果您不在乎,可以忽略它,因为失败的唯一原因是现有密钥) 或

  • 覆盖现有值(对于它来说,写cache[id] = ps; 是最简单且通常完全足够的,但如果你想避免元素对的默认构造,那么你可以使用find,然后覆盖现有的或emplace 必要时添加新元素)。

有时您可能需要考虑将一个或两个参数标记为可移动(即您不关心在emplace 返回后局部变量处于什么(有效)状态,这可能允许额外的优化)。您可以从上面的原型中看到,使用 &amp;&amp; 接受参数,这暗示了执行此类优化的能力。

cache.emplace(std::move(id), std::move(ps));

【讨论】:

  • 在这种情况下,emplace 不会做它的设计。
  • @PSIAlt 这不是真的(鉴于我对它的设计目的的理解,这可能比某些人所希望的要少)但它也很模糊,我没有什么有意义的争议......需要详细说明吗?
  • emplace 接收参数列表,将转发给新对象的构造函数。因为您将std::string 作为初始化列表 - 将使用默认复制构造函数创建对象,这在 cpu 内存方面不是最有效的。
  • @PSIAlt 我想你可能的意思是......问题并没有说明id 的类型是什么 - 它可能是例如将const char* 转换为字符串文字,在这种情况下,std::string 将在需要时构造一次 - 也没有任何关于 Aps 的类型,但我同意它可能是某些类型的考虑因素,如果有的话在emplace 行本身之前构造复杂变量的任何实际原因。 emplace 参数上的 std::move() 是不需要保留本地副本的自然解决方案。
  • 很好的讨论,但是...我不是说 C++11(相应地更新了问题)
【解决方案2】:

第三个显然在时间方面较差,因为它在实际执行插入时需要在映射中进行两次查找(一次用于find(),一次用于[])。所以坚持insert() 函数。

现在的问题是:std::make_pair() 还是 std::pair&lt;A, B&gt;()?遵循DRY的原则通常是一个好主意,这意味着你不应该重复类型,所以使用std::make_pair()

如果你有 C++11,最好的选择是使用emplace():

cache.emplace(id, ps);

附带说明,将地图键入为std::map&lt;const std::string, A&gt; 是没有意义的。地图中的键已经是不可变的;仅使用std::map&lt;std::string, A&gt; 更简洁(并且不会产生 WTF 时刻),而且您将很难找到这两者不同的情况。

【讨论】:

  • 根据重复键所需的行为,[] 可能会更好。尽管考虑到有问题的if 守卫,但这可能没有实际意义。
  • @Xarn std::map 不允许重复键。当密钥已经存在时,OP 的所有 3 个示例都保持不变,所以我认为这是预期的行为。在这种情况下[] 可以做得更好吗?
  • std::map&lt;...&gt;::value_type(id,ps)pairmake_pair 解决方案好得多。
【解决方案3】:
cache.insert(std::make_pair(id,ps));

这将复制idps。如果他们有“沉重”的复制构造函数,这将浪费一些时间和内存。

cache.insert(std::pair<std::string,A>(id,ps));

它确实类似于make_pair

if(cache.find(id) == cache.end()){
 cache[id] = ps;
}

最好不用检查:cache[id] = ps;。但是,在第一次插入元素时,将构造 decltype(ps) 类型的“默认”对象。然后,将其分配(使用operator=)到ps。如果它很重,这也可能是个问题。但是,如果 swap 方法不可用,我认为它是首选方式。

cache.emplace(id, ps);

这可能看起来像零拷贝 emplace,但事实并非如此。 emplace 接收构造函数参数,所以会调用默认的拷贝构造函数。还有它的 C++11。

我认为最有效的方法是

cache[id].swap(ps);

这应该制作最少数量的副本,但缺点是您的ps 将被重置(或包含旧值)。

【讨论】:

  • 其实我一直在寻找这样的答案——比较insert[ ]之间的复制操作。这是否意味着insert[ ] 无论如何都会复制?这是否意味着它们差别不大?
  • @rahman 这可能取决于实现。使用insert,它可以是复制构造函数+赋值操作。如果[] 你知道它只会创建空+赋值操作,无论如何应该会更快。 swap 将只是交换对象的胆量并且应该有恒定的时间。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-10
  • 1970-01-01
  • 2011-05-30
  • 1970-01-01
  • 2023-03-27
相关资源
最近更新 更多