【问题标题】:Why does setting an element of a map to its size increments the size *before* assigning it?为什么将地图的元素设置为其大小会在分配它之前增加大小?
【发布时间】:2013-08-05 02:09:57
【问题描述】:

这是一种常见的模式,用于在标记进入时对其进行索引:检查标记是否在地图中,如果没有,则将其添加到地图中,并指定地图的大小。

在 C++ 中执行此操作时,它会意外地增加地图的大小在进行分配之前

#include <cstdio>
#include <map>
using namespace std;

int main() {
    map<char, int> m;
    printf("Size before adding: %d\n", m.size());
    m['A'] = m.size();
    printf("Size after adding: %d\n", m.size());
    printf("What was added: %d\n", m['A']);

    return 0;
}

打印出来:

Size before adding: 0
Size after adding: 1
What was added: 1

按照我的理解,它应该评估右侧为零,然后将其传递给将“A”和零放入地图的函数。但是它似乎在它开始分配后对其进行评估,这没有意义。

在赋值操作之前不应该评估右手边吗?

【问题讨论】:

  • 不,我很确定这是 UB。
  • @Borgleader:这是未指定的行为。
  • @Nawaz 哦,对了,未指定
  • 只是一个想法。你能用struct X { char c; int id; }; set&lt;X&gt; s代替map&lt;char, int&gt; m;吗?使用计数器生成id
  • @BenjaminLindley:在这种情况下,这不是i。在这种情况下,这是m,它是用户定义的类型。它出现在两边。 size_type 的创建和初始化发生在 inside 函数 operator[] 后面是序列点,实际上是许多序列点,然后 assignment 发生在用户代码中(函数外)。它们之间有很多序列点。我没有看到 UB。

标签: c++ map assignment-operator


【解决方案1】:

这是[] 运算符的近似实现,从 stl 头文件编辑

mapped_type& operator[](const key_type& key){
auto itr = lower_bound(key);
// itr->first is greater than or equivalent to key.
if (itr == end() || comp_func(key, (*itr).first))
      itr = insert(itr, value_type(key, mapped_type()));
return (*itr).second;
}

所以你可以看到它首先插入的新元素,从而将地图大小增加了 1

参考std::map::operator[]

如果 key 不匹配容器中任何元素的 key,则 函数使用该键插入一个新元素并返回一个引用 到它的映射值。请注意,这总是增加容器 大小加一,即使没有为元素分配映射值( 元素是使用其默认构造函数构造的)。

编辑:

正如其他人指出的那样,m['A'] = m.size(); 会导致未指定的行为,切勿使用此类语句,而是可以先计算大小,然后将其分配给新键。

【讨论】:

    【解决方案2】:

    但它似乎在开始分配后对其进行评估......

    赋值中的计算顺序实际上是未指定的(未指定先计算左手还是右手表达式),如this question的答案所示。

    如果首先评估m.size(),那么您的代码将按预期工作,但不能保证这种行为,另一个实现可能首先评估m['A'],与您的情况相同。必须避免这些模棱两可的情况。

    最好做这样的事情

    auto size = m.size();
    m['A'] = size;
    

    您可以保证在元素分配之前首先评估大小查询。

    LIVE CODE with the improvement..

    【讨论】:

      【解决方案3】:

      没有。

      (§5.17/1):“在所有情况下,赋值顺序在左右操作数的值计算之后,赋值表达式的值计算之前。”

      但是请注意,虽然赋值发生在计算左右操作数之后,但在计算左右操作数本身之间没有指定顺序。因此,可以先评估左侧,然后评估右侧,反之亦然。

      【讨论】:

        【解决方案4】:

        这种行为是未指定的,学究式的。

        但你的情况是这样的:

        m['A'] = m.size();
        

        m 是一个 std::map,如果密钥不存在,它会创建一个新条目。

        在您的情况下,键 'A' 不存在,因此它创建条目,并返回对值(默认创建)的引用,然后在您的情况下分配给 m.size()

        如上所述,行为未指定,因为操作数的评估顺序未指定,这意味着m.size() 可能在m['A'] 之前被评估。如果是,那么m['A'] 将是0,而不是1

        【讨论】:

        • 对,所以操作数的求值顺序在 C++ 中一般是未指定的?
        • @EvgeniSergeev:是的,所有运算符都是如此,除了逗号运算符,在这种情况下,顺序被定义为从左到右。跨度>
        • ... 以及 or (||)、and (&amp;&amp;) 运算符也定义了顺序。
        • @EvgeniSergeev:C++ 也没有定义函数参数的求值顺序,所以像assign(key, value); 这样的东西,keyvalue 仍然以未指定的顺序求值。这就是为什么您通常应该避免重载 operator,operator||operator&amp;&amp; 的部分原因——您失去了为正常运算符保证的排序。
        • 在 C++17 中,这是现在定义的行为,因为保证左侧在右侧之前排序。您介意更新答案吗,或者如果您愿意,我可以?谢谢。
        猜你喜欢
        • 1970-01-01
        • 2011-06-13
        • 2019-02-06
        • 2010-12-28
        • 1970-01-01
        • 1970-01-01
        • 2021-11-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多