C++ -迭代std::list并删除元素时出错

gstyhher  于 2023-06-25  发布在  其他
关注(0)|答案(1)|浏览(249)

这是我的代码:

std::list<GameMatch*> games_;

void GameMatchManager::ClearEndedGames()
{
    if(games_.empty())
        return;

    auto it = games_.begin();
    while (it != games_.end())
    {
        auto game = *it;
        if(game->MatchEnded())
        {
            games_.erase(it);
            game->Destroy();
        }
        ++it;
    }
}

void GameMatch::Destroy()
{
    std::cout << "Destoying " << GetMatchId() << std::endl;
    delete this;
}

这是我得到的错误:

为什么我在顶部执行if(games.empty()) return;时,在第一个位置得到这个错误?
这不应该停止games_列表的迭代吗?为什么我会得到这个错误,我如何修复它?

c6ubokkw

c6ubokkw1#

显而易见的问题是:您使用的迭代器无效。从std::list中删除一个元素时,指向other元素的迭代器仍然有效,但被删除元素的迭代器处于未定义状态。一个可能的解决方案是在删除之前迭代一步:

template <typename List>
void ConsumeList(List&& list) {
  auto last_it = list.begin();
  if (last_it != list.end()) {
    for (auto it = std::next(last_it); it != list.end(); ++it) {
      list.erase(last_it);  // last_it becomes invalid here.
      last_it = it;         // Never mind, we make it valid again.
    }
    list.erase(last_it);    // Don't forget this!
  }
}

其他注解,大多与问题无关:
1.避免使用Destroy()等方法;它可能做了一些~GameMatch()应该做的事情,但是没有来自编译器的与RAII相关的强保证。更喜欢RAII而不是“自制”资源管理。
1.不要**使用原始指针。你几乎从来不需要它们。
)唯一的例外是当您实现低级别的链接数据结构时。然后(也只有在那时)使用原始指针才有意义。
1.最好不要直接分配对象(使用智能指针或其他方式)
。相反,让STL容器处理分配。直接嵌入对象(std::list<GameMatch>而不是std::list<GameMatch*>)。指针跳跃越少,效率就越高。
这里再次使用ConsumeList(),这次是在buildable and runnable上下文中:

#include <iostream>
#include <iterator>
#include <list>
#include <memory>
#include <string>
#include <string_view>
#include <utility>

namespace {

struct GameMatch {
  GameMatch(std::string_view match_id) : match_id_{match_id} {}
  std::string_view GetMatchId() const { return match_id_; }
  ~GameMatch() { std::cout << "Destoying " << GetMatchId() << std::endl; }

 private:
  const std::string match_id_;
};

template <typename List>
void ConsumeList(List&& list) {
  auto last_it = list.begin();
  if (last_it != list.end()) {
    for (auto it = std::next(last_it); it != list.end(); ++it) {
      list.erase(last_it);
      last_it = it;
    }
    list.erase(last_it);
  }
}

}  // namespace

int main() {
  std::list<GameMatch> embedded_list;
  embedded_list.emplace_back("Embedded Match A");
  embedded_list.emplace_back("Embedded Match B");
  embedded_list.emplace_back("Embedded Match C");

  std::list<std::unique_ptr<GameMatch>> pointer_list;
  pointer_list.emplace_back(std::make_unique<GameMatch>("Allocated Match A"));
  pointer_list.emplace_back(std::make_unique<GameMatch>("Allocated Match B"));
  pointer_list.emplace_back(std::make_unique<GameMatch>("Allocated Match C"));
  
  ConsumeList(std::move(embedded_list));
  ConsumeList(std::move(pointer_list));
}

请注意,忘记运行ConsumeList()不会在最后导致任何内存泄漏,所有列表元素都将被正确释放。这是使用RAII和避免原始指针的关键优势。

相关问题