【问题标题】:Rationale for C++'s std map insert semantics?C++ 的标准映射插入语义的基本原理?
【发布时间】:2012-05-22 00:57:14
【问题描述】:

我对@9​​87654325@ 的语义有点困惑。我的意思是,我没有抱怨 - 标准就是标准,API 就是它的样子。不过,

insert

插入操作检查每个插入的元素是否 另一个元素已经存在于容器中,具有相同的键 值,如果是,则不插入元素并且其映射值不 以任何方式改变。

而且 - 只有在其单参数版本pair<iterator,bool> insert ( const value_type& x ); 中,它才会告诉您它是否甚至将(新的,可能不同的)值插入到键中。据我了解,如果键已经存在,迭代器版本会默默地忽略插入。

对我来说,这简直是违反直觉的,我本来希望在插入时会覆盖值部分并丢弃旧值部分。显然,STL 的设计者有不同的想法——任何人都知道(历史)基本原理,或者可以对现有语义如何(更)有意义给出彻底的解释?

举例:

std::map等单键映射中实现插入有几种基本方法:

  • 插入,如果已经存在则替换
  • 插入,如果已经存在则忽略(这是 std::map 的行为
  • 插入,如果已经存在则抛出错误
  • 插入,如果已经存在 UB

我现在正试图理解为什么insert_or_ignoreinsert_or_replace(或insert_or_error)更有意义!


我查看了我的TC++PL 副本(不幸的是我只有德语版),有趣的是,Stroustrup 在第 17.4.1.7 章(map 的列表操作)中写道:(抱歉粗略的翻译来自德语)

(...) 通常情况下,一个键(原文如此!)是否是新的并不重要 在调用 insert() 之前插入或已经存在 (...)

在我看来,这仅适用于 set,而不适用于 map,因为对于地图,如果提供值已插入或旧值保留在地图中。 (这显然与密钥无关,因为它是等效的。)


注意:我知道operator[],我知道Effective STL 的第24 项以及那里提出的efficientAddOrUpdate 函数。我只是想知道insert 语义的基本原理,因为我个人觉得它们违反直觉。

【问题讨论】:

  • 好吧,您没有要求它修改现有值,而是要求它插入一个(新)值。我同意更一致地报告失败将是一件好事。您仍然可以取消引用返回的迭代器并检查是否存在新值或旧值,甚至可以使用该迭代器来更新现有值。
  • 如果要替换/创建,请使用operator[]
  • 这里有一些代码可以将"insert forcefully" 转换为地图。
  • @KerrekSB:为什么不简单地使用惯用的operator[]
  • @MatthieuM.:如果您需要根据值是否已经存在做出一些额外的决定和/或记录怎么办?

标签: c++ map standard-library


【解决方案1】:

我并不声称知道做出这个决定的最初理由,但要编造一个理由并不难。我认为;-)

“插入或忽略”的当前行为使得实现其他两个非常容易——至少对于我们这些不高于创建和使用非成员函数来补充标准库功能的人(“它是不够 OOP-y!”)。

示例(当场写的,所以可能存在错误):

template<typename Map>
void insert_or_update(Map& map, typename Map::value_type const& x)
{
  std::pair<typename Map::iterator, bool> result = map.insert(x);
  if (!result.second)
    result.first->second = x.second; // or throw an exception (consider using
                                     // a different function name, though)
}

请注意,上面的函数与operator[] 并没有太大区别——是的,它避免了默认初始化,但同时(因为我很懒)它未能利用移动语义你最新的 STL 可能已经支持operator[]

无论如何,map 的任何其他插入行为都会使实现其他行为变得更加乏味,因为map::find 仅在键不在映射中时才返回结束标记。在&lt;algorithm&gt;(尤其是lower_bound)的帮助下,当然,仍然可以编写高性能的附属函数,而不会淹没它们的实现细节和丑陋的通用结构,如循环;-)。

【讨论】:

    【解决方案2】:

    我不知道官方的理由,但我会注意到 operator[] 的双重性。

    很明显,人们会喜欢两种插入方式:

    • 纯添加剂
    • 加性/破坏性

    如果我们将map 视为数组的稀疏表示,那么operator[] 的存在是有意义的。我不知道预先存在的字典是否存在并规定了这种语法(也许,为什么不)。

    另外,所有 STL 容器有几个insert 的重载,并且接口的这种相似性是允许泛型编程的原因。

    因此,我们至少有两个 API 竞争者:operator[]insert

    现在,在 C++ 中,如果您阅读:

    array[4] = 5;
    

    很自然,索引4 处的单元格内容已被破坏性更新。因此,map::operator[] 应该返回一个引用以允许这种破坏性更新是很自然的。

    此时,我们现在还需要一个纯加法版本,并且我们有这个insert 方法。为什么不呢?

    当然可以给insert 赋予与operator[] 相同的语义,然后继续在顶部实现insert_or_ignore 方法。不过,这将是更多的工作。

    因此,虽然我同意这可能会令人惊讶,但我认为我的推理并没有太大缺陷,并且可能是对导致我们来到这里的情况的可能解释:)


    关于您提出的替代方案:

    • 插入,如果已经存在 UB

    还好不是!

    • 插入,如果已经存在则抛出错误

    只有 Java(及其衍生产品)是异常疯狂的。 C++ 是在异常情况下使用异常的时代构思的。

    • 插入,如果已经存在则替换
    • 插入,如果已经存在则忽略(这是 std::map 的行为)

    我们同意选择是其中之一。请注意,即使map 选择了第二个选项,它并没有完全忽略该项目已经存在的事实,至少在单个项目版本中,因为它警告您该项目没有插入。

    【讨论】:

    • +1,虽然我不确定我们真的需要一个纯粹的加法函数,因为我们总是可以先调用find()。 (insert 要么/或 op[])但也许二元性是/是一个很好的理由。
    • @Martin:我同意find 可以创建一个纯粹的加法函数。不过,它需要一些样板。
    • 是的,有趣的是,您现在需要类似的样板文件(或辅助 fn)来实现高效的 insert_or_replace :-)
    • @Martin:更糟糕的是,它需要一个默认构造函数也是一个问题(否则使用map就不需要)。哦,好吧...
    • 可惜 C++ 没有operator[]=(key, value)
    【解决方案3】:

    insert 方法不是您要寻找的,听起来像... insert 方法的作用正是顾名思义...插入值。我同意在某些情况下创建一个不存在的值并替换存在的值的能力很重要,但在其他情况下,你真的宁愿不处理异常、返回值等,如果你只是仅当值不存在时才想进行插入。

    听起来您正在寻找的方法(如上面的 BoBTFish 所示)可能是 [] 运算符。像这样使用它:

    myMap["key"] = "value";
    

    这将遍历您的地图并找到键“key”,并将相应的值替换为“value”。如果密钥不存在,它将创建它。这两种方法在不同的情况下都非常有用,我发现自己使用这两种方法都取决于我的需要。

    【讨论】:

    • 我编辑了你的帖子。首先,不要(明确地)签名,这是不受欢迎的(无论如何您的用户仍然可见);其次,请检查markdown语法以了解如何格式化内联代码sn-ps和多行代码sn-ps。感谢您的回答并欢迎 SO :)
    【解决方案4】:

    pair&lt;iterator,bool&gt;

    如果 bool 部分为 false,您可以只更新返回的迭代器的 value 部分,以使用相同的键更新现有项。

    【讨论】:

    • 问题提到了这一点。您忽略了提到std::map::insert 的其他四个重载的问题部分。
    【解决方案5】:

    insert() 开始,您不会期望容器中的现有对象会被触及。这就是为什么它不碰它们的原因。

    【讨论】:

    • 不回答 为什么它不报告错误? 问题的一部分,但是它回答了问题的一部分。函数的行为是相当直观,期望通过insert 调用来修改现有元素会相当不直观。
    • 没有冒犯的意思,但你是在模仿函数的定义语义,而不是提供理由。 为什么我不希望触及现有元素?因为它被称为“插入”而不是“插入或替换”或什么?再说一次,对我来说这是反直觉的,当我被告知“不,不是”时,它并不能帮助我理解。 :-)
    • @Martin:insert_or_ignore(如在 SQLite 中)不是比 insert_or_replace 更好的名字吗?
    • @dan04 - 对于指定的语义是。 (Insert_or_replace 是我个人认为更直观的插入和替换。)
    猜你喜欢
    • 1970-01-01
    • 2012-10-19
    • 1970-01-01
    • 2012-04-06
    • 2013-12-03
    • 1970-01-01
    • 1970-01-01
    • 2011-05-22
    • 2011-01-21
    相关资源
    最近更新 更多