什么时候应该使用 std::map::at 来检索地图元素

When I should use std::map::at to retrieve map element

我在网络上阅读了不同的文章并在 Whosebug 上提出了问题,但对我来说,不清楚是否存在使用 std::map::at 检索地图元素更好的特殊情况。

根据 definitionstd::map::at

Returns a reference to the mapped value of the element identified with key k.

If k does not match the key of any element in the container, the function throws an out_of_range exception.

对我来说只有当你 100% 确定具有特定键的元素存在时才值得使用 std::map::at 的情况,否则你应该考虑异常处理。

  1. 是否有任何情况 std::map::at 被认为是最有效和优雅的方式?在什么情况下你会推荐使用 std::map::at ?
  2. 当有可能没有带有这样一个键的元素时,使用 map::find() 更好吗?而且 map::find() 它是更快更优雅的方法?
if ( map.find("key") != map.end() )
{
    // found 

} else
{
    // not found
}

p.s

map::operator[] 有时会很危险,因为如果一个元素不存在,它就会插入它。

已编辑: 链接以某种方式相关 link 1 link 2 link 3 link 4 link 5 link 6

如果找不到元素,

std::map::at() 会抛出 out_of_range 异常。这个异常是一种 logic_error 异常,从使用的角度来看,它对我来说是 assert() 的一种同义词:它应该用于报告程序内部逻辑中的错误,比如违反逻辑先决条件或 class 不变量。

此外,您可以使用 at() 访问常量映射。

所以,对于你的问题:

  1. 在访问 const 映射和元素缺失是逻辑错误时,我建议使用 at() 而不是 []
  2. 是的,当你不确定元素是否在此处时,最好使用 map::find():在这种情况下,这不是逻辑错误,因此抛出和捕获 std::logic_error 异常不会很优雅编程方式,即使我们不考虑性能。

这取决于此功能的要求以及您构建项目的方式。如果你应该 return 一个对象而你不能因为它没有找到那么它给你留下了两个关于如何处理它的选择。您可以通过异常,也可以 return 某种表示未找到任何东西的哨兵。如果您想抛出异常,请使用 at() 因为将为您抛出异常。如果您不想抛出异常,请使用 find(),这样您就不必处理针对 return 哨兵对象的异常。

我想区别在于语义。

std::map::at() 在我的机器上看起来像这样:

mapped_type&
at(const key_type& __k)
{
    iterator __i = lower_bound(__k);
    if (__i == end() || key_comp()(__k, (*__i).first))
        __throw_out_of_range(__N("map::at"));
    return (*__i).second;
}

如您所见,它使用 lower_bound,然后检查 end(),比较键,并在需要的地方抛出异常。

find() 看起来像这样:

iterator
find(const key_type& __x)
{ return _M_t.find(__x); }

其中_M_t是存储实际数据的红黑树。显然,这两个函数具有相同的(对数)复杂度。当您使用 find() + 检查 end() 时,您所做的几乎与 at 所做的相同。我会说语义差异是:

  • 当您在特定位置需要一个元素,并且您假设它就在那里时,请使用 at()。在这种情况下,需要的地方缺少元素的情况是异常的,因此at()抛出异常。
  • 当您需要在地图中查找元素时使用find()。在这种情况下,元素不存在的情况是正常的。另请注意 find() returns 一个迭代器,您可以将其用于除简单地获取其值之外的其他目的。

findoperator[]at 三个都有用。

  • find 如果您不想意外插入元素,但仅在它们存在时才采取行动。

  • at 如果您希望某些东西应该在地图上并且如果它不在地图上则抛出异常,则很好。它还可以比 find 更简洁地访问 const 地图(你不能使用 op[]

  • op[] 如果您 想要 插入一个默认元素,例如对于将 int 0 对于第一次遇到的每个单词(成语 words[word]++;)。

我认为,这取决于您的用例。 std::map::at() 的 return 类型是对找到的元素值的左值引用,而 std::map::find() return 是迭代器。您可能更喜欢

return myMap.at("asdf"s) + 42;

在表达式中比较详细

return myMap.find("asdf"s)->second + 42;

当你在表达式中使用std::map::at()的结果时,你期望该元素存在,并且将缺少元素视为错误。因此,异常是处理该问题的不错选择。

如您所述,可以通过三种不同的方式访问地图中的元素:at()operator[]find()(还有 upper_boundlower_boundequal_range,但这些用于更复杂的情况,您可能希望找到 next/previous 元素等)

那么,什么时候应该使用哪一个?

operator[] 基本上就是 "if it does not exist, create one with a default-constructed mapped element"。这意味着它不会抛出(除了在内存分配抛出或键或值构造函数之一抛出的极端情况下),并且您肯定会获得对您查找的元素的引用 - 现有元素或新创建的元素.

at() 如果该键没有元素则抛出。由于您不应该对正常程序流使用异常,因此使用 at() 就是说 "I am sure there is such an element." 但是如果您错了,您会得到一个额外的好处(而不是未定义的行为)。如果您不确定该元素是否存在,请不要使用它。

find() 表示 "there may or may not be such an element, let's see..." 并为您提供了对两种情况做出不同反应的可能性。因此,这是更通用的方法。

map::at() return 是一个左值引用,当您通过引用 return 时,您可以利用它所有可用的好处,例如方法链接。

示例:

  map<int,typ> table;
  table[98]=a;
  table[99]=b;

  table.at(98)=table.at(99);

operator[] 也 return 通过引用映射值,但如果未找到搜索的键,它可能会插入一个值,在这种情况下容器大小增加一。

这需要您格外小心,因为您必须照顾 iterator invalidation

Am I right that it is better to use map::find() when there is a possibility to not have element with such a key? And map::find() it is faster and more elegant approach?

是的,从语义上讲,当您不确定 element.Makes 代码是否存在时使用 find() 是有意义的,即使对于新手也更容易理解。

至于时间效率,map 通常实现为 RB-tree/some 平衡二叉搜索树,因此 find() 的复杂度为 O(logN)。

C++ Spec:

T& operator[](const key_type& x);
Effects: If there is no key equivalent to x in the map, inserts value_type(x, T()) into the map. Requires: key_type shall be CopyInsertable and mapped_type shall be DefaultInsertable into *this. Returns: A reference to the mapped_type corresponding to x in *this. 4 Complexity: Logarithmic.

T& at(const key_type& x);
const T& at(const key_type& x) const; Returns: A reference to the mapped_type corresponding to x in *this. Throws: An exception object of type out_of_range if no such element present. Complexity: Logarithmic.

与这里的大多数现有答案相反,请注意实际上有 4 种方法与在地图中查找元素相关(忽略 lower_boundupper_boundequal_range,不太精确):

  • operator[] 仅存在于非常量版本中,如前所述,如果元素不存在,它将创建该元素
  • at(),在 C++11 中引入,returns 如果元素存在则引用该元素,否则抛出异常
  • find() returns 元素的迭代器(如果存在)或 map::end() 的迭代器(如果不存在)
  • count()returns这样元素的个数,在一个map中,这个是0或者1

现在语义已经清楚了,让我们回顾一下什么时候使用哪个:

  • 如果您只想知道某个元素是否存在于 map 中(或不存在),则使用 count().
  • 如果你想访问元素,并且它应该在 map 中,那么使用 at().
  • 如果你想访问元素,不知道它是否在map中,那么使用find();不要忘记检查生成的迭代器是否不等于 end().
  • 的结果
  • 最后,如果您希望在元素存在时访问该元素或在元素不存在时创建它(并访问它),请使用 operator[];如果您不想调用类型默认构造函数来创建它,请适当地使用 insertemplace