当我添加一个新元素时,Unordered_map 的行为很奇怪

Posted

技术标签:

【中文标题】当我添加一个新元素时,Unordered_map 的行为很奇怪【英文标题】:Unordered_map acts strange when I add a new element 【发布时间】:2019-05-01 10:43:06 【问题描述】:

这行不正常:

for (auto prod : productions_[*productionNonterm])
                productions_[nonterminal].push_back(prod);

如果 productions_[*productionNonterm] 只有 1 个元素,则一切正常。但是如果它至少有 2 个元素,productionNonterm 会被修改,我不知道为什么。

vector<string> nonterminals_;
unordered_map<string, vector<string>> productions_;

for (const auto &nonterminal : nonterminals_) 
    for (auto productionNonterm = productions_[nonterminal].begin(); productionNonterm != productions_[nonterminal].end(); ++productionNonterm) 
        if (cntNonterminalsInProduction(*productionNonterm) == 1 && cntTerminalsInProduction(*productionNonterm) == 0) 
            nonterminals_.erase(find(nonterminals_.begin(), nonterminals_.end(), *productionNonterm));

            for (auto prod : productions_[*productionNonterm])
                productions_[nonterminal].push_back(prod);

            productions_[*productionNonterm].erase(productions_[*productionNonterm].begin(), productions_[*productionNonterm].end());

            productions_[nonterminal].erase(productionNonterm);
            --productionNonterm;

        
    

【问题讨论】:

修改 std::vector 会使该向量中的所有迭代器无效。因此,您不能在通过基于范围的 for 循环(它只是映射到从 .begin().end() 的普通 for 循环)对其进行迭代时修改 nonterminals_productions_[nonterminal] 也是如此,它也是一个向量。你到底想在这里实现什么?这看起来应该以更易读的形式完全重写...... 具体应该根据什么逻辑删除和添加元素? 【参考方案1】:

迭代器 productionNonterm 的问题,它在循环期间变得无效:

一旦你开始循环

    for (auto prod : productions_[*productionNonterm])
        productions_[nonterminal].push_back(prod);

您将使用一个有效的迭代器 (productionNonterm) 指向 productions_[nonterminal] 中的一个(第一个)元素。

但在第一次执行循环体后 - 向量 productions_[nonterminal] 将重新分配其元素(由于增长)并且您的指针(迭代器)将失效...

【讨论】:

【参考方案2】:

在迭代集合的同时修改集合很棘手,而且通常不值得在容器中支持它的开销。在这种情况下(向量),一旦向量在增长时重新分配,您就会使迭代器(即指向向量存储的指针)无效。

看看vector::push_back,特别是关于迭代器有效性的讨论。

在实践中,您可以通过调整向量大小来避免这个特殊问题,但也存在跟踪相对于迭代器放置新元素的位置等问题。通常,它会导致难以遵循的代码,即使当它是正确的(或者更糟的是,看起来但有一些微妙的问题)。

我建议您重写为两遍方法,在一遍中收集要添加或删除的元素,然后删除它们等。除非您有非常好的(并且经过衡量!)性能理由不这样做,否则它会继续更容易理解和维护。回想一下,vector::erase 可以有一个范围。

还有一个问题:这里的顺序重要吗?如果您使用 std::unordered_set,在这种情况下您不会遇到这个特殊问题。

在“修复”这个方法之前,看看你对这种方法的看法。

【讨论】:

以上是关于当我添加一个新元素时,Unordered_map 的行为很奇怪的主要内容,如果未能解决你的问题,请参考以下文章

Qt - std::unordered_map - 销毁时间

如何在删除元素时防止重新散列 std::unordered_map?

Flutter:将新元素添加到列表时保持滚动偏移

当我将新单元格添加到集合视图时,我不断收到布局错误

容器————unordered_map

unordered_map和map的区别