【问题标题】:What causes the stored std::list::iterator to become invalid?是什么导致存储的 std::list::iterator 变得无效?
【发布时间】:2020-08-13 20:46:23
【问题描述】:

编辑:下面的代码可以在https://wandbox.org/permlink/1Qry83quzoDveYDi上运行
我实施了 cmets 的各种建议,但不幸的是,我仍然不清楚为什么在 this specific std::list::iterator(第 86 行)擦除项目会在运行时崩溃。下面给出的所有解释似乎都在肯定迭代器在这一点上应该仍然有效。


我的印象是,在将项目插入列表时,std::list 中项目的迭代器不会失效(请参阅this excellent post)。

但是,在下面的代码中,行
items.at(noOfitems-2)->erase(iter++);(第 86 行)
使用
malloc: *** error for object 0x100778b28: pointer being freed was not allocated 使程序崩溃。

能否请您帮助我理解为什么(在哪里)这个迭代器 std::list<std::string>::iterator 变得无效,以及如何在不重复找到它的情况下使其工作?

我可能误解了这个错误吗?

#include <iostream>
#include <iostream>
#include <vector>
#include <random>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include <map>
#include <list>
#include <utility>
#include <chrono>
#include <sstream>
#include <tr1/memory>

class Increment;
struct Item;

struct Pairhash {
public:
  template <typename T>
  std::size_t operator()(const T &x) const
  {
      return std::hash<T>()(x) ^ std::hash<T>()(x);
  }
};

struct Item {
    Item() = default;
    std::string name;
    int counter;
    Item(std::string name) : counter(0)
    {
        this->name = name;
    }
    
    bool operator==(const Item& p) const
    {
        return (this->name == p.name);
    }
    
    bool operator<(const Item& p) const
    {
        return (this->counter < p.counter);
    }
};

class Increment {
private:
    std::unordered_map<std::string, std::pair<std::list<std::string>::iterator , std::shared_ptr<Item> >, Pairhash >  itemMap;
    std::vector<std::shared_ptr<std::list<std::string>>> items;
public:
    Increment() = default;
    std::list<std::string>::iterator insertItem(std::string & name , int noOfitems)
    {
        if (noOfitems > items.size())
        {
            items.emplace_back(std::make_shared<std::list<std::string>>(std::initializer_list<std::string>{ name }));
            return items.back()->begin();
        }
        else
        {
            items.at(noOfitems-1)->emplace_back(name);
            return items.at(noOfitems-1)->rbegin().base(); //Last position in list
        }
    }
    
    std::list<std::string>::iterator adjustItemPosition(std::string & name, int noOfitems, std::list<std::string>::iterator & iter)
    {
        if (noOfitems > items.size())
        {
            std::list<std::string> temp{name};
            items.push_back(std::make_shared<std::list<std::string>>(temp));
        }
        else
        {
            items.at(noOfitems-1)->emplace_back(name);
        }
        /* // Works as expected
        auto itr = std::find(items.at(noOfitems-2)->begin(), items.at(noOfitems-2)->end(), name);
        if (itr != items.at(noOfitems-2)->end())
        {
            items.at(noOfitems-2)->erase(itr);
        }
        */
        items.at(noOfitems-2)->erase(iter++); //TODO Crashes
        return items.at(noOfitems-1)->rbegin().base(); //Last position in list
    }
    
    void incrementByOne(std::string name)
    {
        auto it = itemMap.find(name);
        if (it != itemMap.end()) //Item already in map
        {
            it->second.second->counter++;
            it->second.first = adjustItemPosition(name, it->second.second->counter,
                                                    it->second.first);
        }
        else  //New item to be inserted
        {
            std::shared_ptr<Item> temp = std::make_shared<Item>(Item(name));
            temp->counter = 1;
            std::list<std::string>::iterator listIter = insertItem(name, 1);
            itemMap.emplace(name, std::make_pair( listIter, temp));
        }
    }
    
    std::string printTop10() const
    {
        std::stringstream ss;
        auto count(0);
        for (auto it = items.rbegin(); it != items.rend(); ++it)
        {
            for (auto item : **it)
            {
                if (count == 10)
                {
                    break;
                }
                ss << "We have " << itemMap.at(item).second->counter << " " << item << std::endl;
                ++count;
            }
        }
        return ss.str();
    }
};

int main(int argc, const char * argv[]) {
    Increment incrementer;
    std::vector<std::string> names{ "Bananas", "Apples", "Peaches", "Durians", "Hazelnuts", "Avocados", "Pineapples", "Cherries", "Almonds", "Olives", "Eggs", "Yoghurts", "Peas", "Blueberries" };

    for (int i = 0; i < 100; ++i) {
        incrementer.incrementByOne(names.at(i%10));
    }
    std::cout << incrementer.printTop10() << std::endl;
    return 0;
}

【问题讨论】:

  • 您是否检查过items.at(noOfitems-2) 不是nullptr?否则,std::list 迭代器无效的唯一方法是该元素不再存在。这是 std::list 相对于大多数其他顺序容器的唯一具体优势之一,它的迭代器和引用稳定性。
  • 是的,我想到了;使用第 80 - 84 行代替第 86 行时有效。这也使用 noOfItems -2。
  • 好吧,如果指针是nullptr,那将是未定义的行为,因此它们没有明显失败的事实并不能证明它不是nullptr。使用调试器来确保,或者打印一些东西到std::cout,表明它不是nullptr

标签: c++ iterator crash invalidation


【解决方案1】:

是什么导致存储的 std::list::iterator 失效?

删除迭代器指向的元素,无论是通过clearpop_Xerase还是破坏列表。 assign 使该列表中所有元素的迭代器无效。

【讨论】:

  • 根据链接的帖子,情况并非如此。引用stackoverflow.com/questions/6438086/iterator-invalidation-rules:“列表:仅使迭代器和对已擦除元素的引用无效。[26.3.10.4/3]。这适用于擦除、pop_front、pop_back、clear 函数。”
  • @TTT 这与我的回答并不矛盾。特别要注意“只有迭代器和对已擦除元素的引用
  • 我很惊讶地看到有两个 cmets 挑战这个正确答案。
  • 我不想访问已擦除的元素。第 80-84 行证明该元素存在于预期的位置。
  • @PeteBecker 但iter++ 的行为是复制迭代器,增加原始值,然后评估为原始值(在增加之前)。所以iter 应该在使用先前值擦除之前指向下一个元素,不是吗?我看不出有任何原因会使iter 无效,因为它会在擦除发生之前递增以指向其他内容。
【解决方案2】:

您的代码中至少存在一个问题:

std::list<std::string> temp;
temp.push_back(name);
items.emplace_back(std::make_shared<std::list<std::string>>(temp));
return temp.begin();

在这里,您将迭代器返回到已删除列表中的元素。您可能还有其他问题。

【讨论】:

  • 谢谢 SergeyA - 这是我没有考虑过的。让我试着解决这个问题。
  • 不幸的是,将 return temp.begin(); 替换为 return items.back()-&gt;begin(); 会导致同样的崩溃。
  • @TTT 这只是一个优化提示:因为temp 在函数返回时注定要失败,您可以直接窃取其内容,而不是不必要地将其复制到新列表中。 items.emplace_back(std::make_shared&lt;std::list&lt;std::string&gt;&gt;(std::move(temp)));。或者,删除 temp 并在正确的位置一步构建列表:items.emplace_back(std::make_shared&lt;std::list&lt;std::string&gt;&gt;&gt;({ name }));
  • 嗨@cdhowie,感谢您的提示。 std::move 有效并且是一个明显的改进。我无法使用items.emplace_back(std::make_shared&lt;std::list&lt;std::string&gt;&gt;&gt;({ name })); 就地构建工作(无法编译)
  • @TTT 我的错,你必须明确指定参数是一个初始化列表。编译器无法推断出这一点。 items.emplace_back(std::make_shared&lt;std::list&lt;std::string&gt;&gt;(std::initializer_list&lt;std::string&gt;{ foo }));
【解决方案3】:

更换
return items.at(noOfitems-1)-&gt;rbegin().base();

return std::next(items.at(noOfitems-1)-&gt;end(), -1);

在第 63 和 86 行修复了崩溃。
工作解决方案:https://wandbox.org/permlink/I68Szb0XMRKsPZqp

#include <iostream>
#include <iostream>
#include <vector>
#include <random>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include <map>
#include <list>
#include <utility>
#include <chrono>
#include <sstream>
#include <memory>

class Increment;
struct Item;

struct Pairhash {
public:
  template <typename T>
  std::size_t operator()(const T &x) const
  {
      return std::hash<T>()(x) ^ std::hash<T>()(x);
  }
};

struct Item {
    Item() = default;
    std::string name;
    int counter;
    Item(std::string name) : counter(0)
    {
        this->name = name;
    }

    bool operator==(const Item& p) const
    {
        return (this->name == p.name);
    }

    bool operator<(const Item& p) const
    {
        return (this->counter < p.counter);
    }
};

class Increment {
private:
    std::unordered_map<std::string, std::pair<std::list<std::string>::iterator , std::shared_ptr<Item> >, Pairhash >  itemMap;
    std::vector<std::shared_ptr<std::list<std::string>>> items;
public:
    Increment() = default;
    std::list<std::string>::iterator insertItem(std::string & name , int noOfitems)
    {
        if (noOfitems > items.size())
        {
            items.emplace_back(std::make_shared<std::list<std::string>>(std::initializer_list<std::string>{ name }));
            return items.back()->begin();
        }
        else
        {
            items.at(noOfitems-1)->emplace_back(name);
            return std::next(items.at(noOfitems-1)->end(), -1);  // versus return items.at(noOfitems-1)->rbegin().base(); //Crashes
        }
    }

    std::list<std::string>::iterator adjustItemPosition(std::string & name, int noOfitems, std::list<std::string>::iterator & iter)
    {
        if (noOfitems > items.size())
        {
            std::list<std::string> temp{name};
            items.push_back(std::make_shared<std::list<std::string>>(temp));
        }
        else
        {
            items.at(noOfitems-1)->emplace_back(name);
        }
        /* // Works as expected
        auto itr = std::find(items.at(noOfitems-2)->begin(), items.at(noOfitems-2)->end(), name);
        if (itr != items.at(noOfitems-2)->end())
        {
            items.at(noOfitems-2)->erase(itr);
        }
        */
        items.at(noOfitems-2)->erase(iter++); //TODO Crashes
        return std::next(items.at(noOfitems-1)->end(), -1); //versus return items.at(noOfitems-1)->rbegin().base(); //Crashes
    }

    void incrementByOne(std::string name)
    {
        auto it = itemMap.find(name);
        if (it != itemMap.end()) //Item already in map
        {
            it->second.second->counter++;
            it->second.first = adjustItemPosition(name, it->second.second->counter,
                                                    it->second.first);
        }
        else  //New item to be inserted
        {
            std::shared_ptr<Item> temp = std::make_shared<Item>(Item(name));
            temp->counter = 1;
            std::list<std::string>::iterator listIter = insertItem(name, 1);
            itemMap.emplace(name, std::make_pair( listIter, temp));
        }
    }

    std::string printTop10() const
    {
        std::stringstream ss;
        auto count(0);
        for (auto it = items.rbegin(); it != items.rend(); ++it)
        {
            for (auto item : **it)
            {
                if (count == 10)
                {
                    break;
                }
                ss << "We have " << itemMap.at(item).second->counter << " " << item << std::endl;
                ++count;
            }
        }
        return ss.str();
    }
};

int main(int argc, const char * argv[]) {
    Increment incrementer;
    std::vector<std::string> names{ "Bananas", "Apples", "Peaches", "Durians", "Hazelnuts", "Avocados", "Pineapples", "Cherries", "Almonds", "Olives", "Eggs", "Yoghurts", "Peas", "Blueberries" };

    for (int i = 0; i < 100; ++i) {
        incrementer.incrementByOne(names.at(i%10));
    }
    std::cout << incrementer.printTop10() << std::endl;
    return 0;
}

我相信https://stackoverflow.com/a/33851951 给出了为什么会这样的答案,即指向反向迭代器的指针实际上是对它的原始引用的副本,当它超出范围时会被删除。 http://cplusplus.github.io/LWG/lwg-defects.html#2360

【讨论】:

    猜你喜欢
    • 2017-04-21
    • 2014-08-15
    • 2011-07-30
    • 1970-01-01
    • 1970-01-01
    • 2020-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多