【问题标题】:Unordered_map acts strange when I add a new element当我添加一个新元素时,Unordered_map 的行为很奇怪
【发布时间】: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] 也是如此,它也是一个向量。你到底想在这里实现什么?这看起来应该以更易读的形式完全重写......
  • 具体应该根据什么逻辑删除和添加元素?

标签: c++ c++11 stl


【解决方案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,在这种情况下您不会遇到这个特殊问题。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-01
      • 1970-01-01
      • 2023-03-14
      相关资源
      最近更新 更多