【问题标题】:How can I erase elements from a vector given a list of iterator?给定迭代器列表,如何从向量中删除元素?
【发布时间】:2018-07-02 23:26:37
【问题描述】:

我有一个 int 向量和一个映射,其中包含一些指向该向量的迭代器作为值。我需要从地图中删除键,以及值指向的向量元素。我的代码看起来像这样:

using RenderData = int;
using Element = std::string;

struct Ref {
    std::vector<RenderData>::iterator ref;
    std::function<int()> update;
    bool should_remove;
};

int main() {
    std::vector<RenderData> ints{1, 2, 3, 4, 5, 6, 7, 8, 9};
    std::unordered_map<Element, Ref> elements;

    // Here, I need to remove some elements, and their associated number
}

我实现了一个类似于this oneerase_if 函数。

所以我的初始代码如下所示:

erase_if(elements, [&](auto&& element) {
    if (element.second.should_remove) {
        ints.erase(element.second.ref);
        return true;
    }

    return false;
});

显然没有用。擦除元素会使其他迭代器指向错误的对象,并且在某些情况下超出范围。所以我尝试了:

std::vector<std::vector<RenderData>::iterator> to_remove;

erase_if(elements, [&](auto&& element) {
    // condition based on the string content
    if (element.second.should_remove) {
        to_remove.emplace_back(element.second.ref);
        return true;
    }

    return false;
});

// Sort in descending order
std::sort(to_remove.begin(), to_remove.end(), std::greater<>{});

// stuff

for (auto&& it : to_remove) {
    ints.erase(it); // nothing can go wrong right?
}

再一次,我有时会擦除错误的元素。

给定存储在某个地图中的迭代器,我可以从向量中删除迭代器指向的元素吗?


更新:

似乎在上一个 sn-p 中我交换了向量中的一些元素,从而擦除了错误的元素。现在它似乎工作了,但我仍然很好奇我们可以用什么方法从迭代器列表中删除向量中的元素。

【问题讨论】:

  • 底线是——不要将迭代器存储到内部向量项。如果调整向量的大小,可能会发生迭代器失效。
  • @PaulMcKenzie 我有一个函数可以在调整大小时将元素从地图重新同步到矢量,所以目前这不是问题。也许有了索引,它可能会更快或更容易做到。尽管您的建议是正确的,但不遵循它会导致我目前生活的痛苦。
  • 我有一个函数,可以在调整大小时将元素从地图重新同步到矢量 -- 只有当你能够确定所有位置时,该函数才有效可能会发生失效。我会使用索引,并在处理每个项目时删除元素go backwards in the array of indices that are to be removed, from highest index to lowest index,而不是转发。
  • @PaulMcKenzie 我会使用索引——这是我担心的,那时我可能需要进行一些重构。
  • 简答:使用索引。长答案,如果您提供更多上下文,例如这些数据结构是什么以及您想对它们执行哪些操作,那么可能会有更好的选择。

标签: c++ vector c++14


【解决方案1】:

使用迭代器版本。 注意:

  1. 迭代器只有在重新分配时才会失效。
  2. 仅用索引替换迭代器不会改变从向量中删除某些值时它们无效的事实。
  3. 使用索引仍然是一种更好的方法,因为以后添加更多元素时它们不会失效。

方法: 使用不应删除的元素创建向量的副本

using Element = std::string;
using RenderData = int;
struct Ref {
    std::vector<RenderData>::iterator itr;
    bool should_remove;
};

struct Main {
    std::vector<RenderData> ints;
    std::unordered_map<Element, Ref> elements;
    void remove_stuff(){
        std::vector<RenderData> localCopy;        
        localCopy.swap(ints);
        ints.reserve(localCopy.size());        
        for(auto it = elements.begin(); it != elements.end();) {
            Ref& ref = it->second;
            if(ref.should_remove) {
                it = elements.erase(it);
            } else {
                ints.push_back(std::move(*ref.itr));
                ref.itr = ints.end() - 1;                
                it++;
            }
        }            
    }
};

链接:https://godbolt.org/g/SouZ5E

【讨论】:

  • 谢谢!我还注意到我在某些操作之间调用了重新同步函数。我设法让它工作,但这个解决方案似乎比我的更可靠。
  • 我设法使排序+擦除循环工作。但是,您的解决方案显然是最好的,即使我最终可能不使用此代码,它也是最完整的并且达到了它的目标。
  • 向量中的迭代器在更多情况下也会失效,而不仅仅是重新分配。更一般的规则是,“如果任何向量元素的地址因操作的副作用而发生变化,则认为所有迭代器都无效。”当然,如果交换两个元素,它会更改这两个元素的元素地址,但不会使其他元素迭代器无效,因此该规则比严格必要的要宽。但是在特殊情况下,您通常会做一些特定的事情并且知道什么时候是安全的。一般来说,假设改变大小的操作会使所有迭代器失效。
  • vector&lt;..&gt;::shrink_to_fit does invalidate iterators 如果ints.size()!=ints.capacity()。所以在remove_stuff 的末尾,如果任何元素被删除,所有ref.itr 都将是无效的迭代器。
  • 删除错误的元素并不是您唯一的问题。删除还会使您不想要删除的元素的迭代器无效。 IE。元素将仍然存在,但迭代器将不再可取消引用。这是list(或forward_list)可能比向量更有意义的使用场景之一。另请注意,您的 erase_if 具有二次时间复杂度 - 更有效的习语是 container.erase(remove_if(beg, end, pred), end)
【解决方案2】:

A 计划: 最好把std::vector换成std::list,std::list的迭代器在擦除后不会失效。

B计划:

std::vector<int> newOne;
copy_if(oldOne.begin(), oldPOne.end(), std::back_inserter(newOne), [](){ //to test not in the map ~~ });
oldOne = std::move(newOne)

【讨论】:

  • std::list 太慢了。计划 B 可能适用于某人,但我个人会避免它,因为它的复杂性和所需的额外分配。
  • 我猜你想快速删除。 U 说 std::list 太慢了,也许这不对。你可以调用 std::list 的 'splice' API,这不会导致元素复制和迭代器不会无效。尝试 'void splice (const_iterator position, list& x, const_iterator i);',将应该删除的添加到新列表中。并删除此列表~~
  • 好吧,确实有很多数据std::list在添加/删除元素时速度更快,但是当它来自迭代时,它会完全破坏缓存:(
  • 你可以提供更多的上下文。如果你坚持使用向量,没关系,你的解决方案是有意义的。但我将使用 'std::set>'(降序)而不是排序。然后 foreach 删除迭代器指向的元素。这个解决方案是基于std::vector的实现。但是你确定这适用于所有平台吗?
【解决方案3】:

使用索引而不是迭代器。替换

struct Ref {
    std::vector<RenderData>::iterator ref;
    std::function<int()> update;
    bool should_remove;
};
std::vector< std::vector<RenderData>::iterator> to_remove;

struct Ref {
    std::size_t ref;
    std::function<int()> update;
    bool should_remove;
};
std::vector< std::size_t> to_remove;

然后你对后代排序的好主意就会奏效。通过索引删除元素调用ints.erase(ints.begin() + ref)

迭代器的排序是一个错误的想法。向量迭代器的实现方式是不可预测的。它们的排序顺序与指针/地址/索引相同的可能性很小。

迭代器擦除(迭代器位置); (直到 C++11)

迭代器擦除(const_iterator pos); (C++11 起)

移除 pos 处的元素。

在擦除点或之后使迭代器和引用失效,包括 end() 迭代器。

【讨论】:

  • 我认为您应该能够依靠对迭代器进行排序,如 std::random_access_iterator_tag “可以与不等式关系运算符(、=)进行比较。”。不过,我并不是说这是个好主意。
  • 除非您更新地图中的剩余引用(迭代器或索引),否则迭代器和索引都不起作用。例如。如果您在 [0]、[1] 和 [2] 的向量中存储了三个项目,并且您删除了项目 [1],则必须将地图中对 [2] 的任何引用从 [2] 更改为 [1] ...
  • @CAF 如果您从头到尾擦除元素,则迭代器保持有效。迭代器有效性(cplusplus.com/reference/vector/vector/erase):指向位置(或第一个)及以后的迭代器、指针和引用无效,所有迭​​代器、指针和对位置(或第一个)之前元素的引用都保证继续引用它们相同的元素通话前提到的。”
  • @S.M.从后面按排序顺序擦除项目并不能解决问题,所有对索引最低的项目后面的剩余项目的引用都需要更新......
猜你喜欢
  • 2015-04-21
  • 1970-01-01
  • 2014-09-04
  • 2016-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-25
  • 1970-01-01
相关资源
最近更新 更多