25

std::map::insert我对's 的语义有点困惑。我的意思是,我没有抱怨 - 标准就是标准,API 就是它的样子。仍然,

insert将要

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

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

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

举例:

有几种基本方法可以在单键映射中实现插入,例如std::map

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

我现在试图理解为什么比(或)insert_or_ignore更有意义!insert_or_replaceinsert_or_error


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

(...) 通常,人们不在乎键(原文如此!)是新插入的还是在调用insert()(...)之前已经存在

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


注意:我知道operator[]并且我知道有效 STL的第 24 项以及那里提出的efficientAddOrUpdate功能。我只是对insert's 语义的基本原理感到好奇,因为我个人发现它们违反直觉。

4

5 回答 5

7

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

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

myMap["key"] = "value";

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

于 2012-05-14T14:33:53.813 回答
6

我不知道官方的理由,但我会注意到与operator[].

很明显,人们会喜欢两种类型的插入:

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

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

此外,所有STL 容器都有多个insert.

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

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

array[4] = 5;

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

在这一点上,我们现在也需要一个纯粹的加法版本,并且我们有这个insert方法。为什么不 ?

当然,可以给出insert相同的语义operator[],然后继续在 top 上insert_or_ignore实现一个方法。不过,这将是更多的工作。

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


关于您提出的替代方案:

  • 插入,UB 如果已经存在

幸运的是,它不是!

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

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

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

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

于 2012-05-14T15:20:40.623 回答
3

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

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

示例(当场编写,因此可能存在错误):

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[]——是的,它避免了默认初始化,但同时(因为我很懒)它没有利用你最多的移动语义—— date STL 可能已经支持operator[].

Anyhow, any other insert behaviour for map would've made it more tedious to implement the others, as map::find only returns an end sentinel if the key isn't already in the map. With the help of <algorithm> (and especially lower_bound) it would, of course, still be possible to write performant accessory functions without drowning them implementation details and ugly generic constructs such as loops ;-).

于 2012-05-19T16:37:18.767 回答
0

insert()您不希望触及容器中的现有对象。这就是为什么它简单不触及它们。

于 2012-05-14T14:25:39.793 回答
0

pair<iterator,bool><- bool 部分不告诉插入是否成功?

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

于 2012-05-14T14:33:15.857 回答