std::vector::erase/remove_if resulting in nullptr

std::vector::erase/remove_if resulting in nullptr

本文关键字:if resulting nullptr in remove vector erase std      更新时间:2023-10-16

我有一个std::shared_ptr<MotionTask>对象的向量,我需要偶尔清理一下。

// this assert passes
assert(std::all_of(d_tasks.begin(), d_tasks.end(),
       [](shared_ptr<MotionTask> task) { return bool(task); }));
// Remove any committed tasks for which the corresponding module has completed
d_tasks.erase(
  remove_if(
    d_tasks.begin(), 
    d_tasks.end(),
    [module](shared_ptr<MotionTask> const& task)
    {
      return task->isCommitted() && task->getModule() == module;
    }
  )
);
// this assert fails
assert(std::all_of(d_tasks.begin(), d_tasks.end(),
       [](shared_ptr<MotionTask> task) { return bool(task); }));

最终的assert失败,因为在任务向量中有一个为null (false)。

我不明白为什么调用erase会使成员无效。我还没能在单元测试中重现这个

是否有可以从上面的代码中观察到的解释,如果没有,我可以尝试调试它吗?

您正在调用单个迭代器std::vector::erase过载。您需要两个迭代器版本:

d_tasks.erase(
  remove_if(
    d_tasks.begin(), 
    d_tasks.end(),
    [module](shared_ptr<MotionTask> const& task)
    {
      return task->isCommitted() && task->getModule() == module;
    }
  ),
  d_tasks.end() // HERE!!
);

单迭代器版本删除单个元素,而erase-remove习语则需要删除一个范围。这是使用两个迭代器版本实现的。

您的问题是您使用的是vector<T>::erase的单个迭代器版本,它擦除了一个元素。

有两种方法可以解决这个问题。第一种是使用vector<T>::erase的双迭代器版本。二是不再使用基于迭代器的算法,而是开始编写基于容器的算法。

template<typename Container, typename Lambda>
Container&& remove_if_erase( Container&& container, Lambda&& closure ) {
  using std::begin; using std::end;
  container.erase(
    std::remove_if(
      begin(container), end(container), std::forward<Lambda>(closure)
    ),
    end(container)
  );
  return std::forward<Container>(container);
}

执行一次删除元素和擦除元素的两个操作。可以编写trait类,使其不仅可以与vector一起工作,还可以与setmap等关联容器一起工作。

我发现一个类似的有用的是sort_unique_erase,它接受一个集合并删除重复项。

通过编写这种基于容器的算法,您的代码既变得更清晰,也更不容易出错,因为您不会在所有地方重复自己。许多基于迭代器的技术都以意想不到的方式悄无声息地失败了,因为一个简单的拼写错误:经过测试的基于容器的算法可以在传入的任何容器上可靠地工作,或者无法编译。