vector::erase 和 std::remove_if 的奇怪行为,其结束范围与 vector.end() 不同

发布于 2024-08-30 12:28:01 字数 592 浏览 1 评论 0原文

我需要从 std::vector 中间删除元素。

所以我尝试了:

struct IsEven {
    bool operator()(int ele)
    {
        return ele % 2 == 0;
    }
};

    int elements[] = {1, 2, 3, 4, 5, 6};
    std::vector<int> ints(elements, elements+6);

    std::vector<int>::iterator it = std::remove_if(ints.begin() + 2, ints.begin() + 4, IsEven());
    ints.erase(it, ints.end());

在此之后,我期望 ints 向量具有:[1, 2, 3, 5, 6]。

在 Visual Studio 2008 的调试器中,在 std::remove_if 行之后,ints 的元素被修改,我猜我陷入了某种未定义的行为这里。

那么,如何从向量的范围中删除元素呢?

I need to remove elements from the middle of a std::vector.

So I tried:

struct IsEven {
    bool operator()(int ele)
    {
        return ele % 2 == 0;
    }
};

    int elements[] = {1, 2, 3, 4, 5, 6};
    std::vector<int> ints(elements, elements+6);

    std::vector<int>::iterator it = std::remove_if(ints.begin() + 2, ints.begin() + 4, IsEven());
    ints.erase(it, ints.end());

After this I would expect that the ints vector have: [1, 2, 3, 5, 6].

In the debugger of Visual studio 2008, after the std::remove_if line, the elements of ints are modified, I'm guessing I'm into some sort of undefined behaviour here.

So, how do I remove elements from a Range of a vector?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

咆哮 2024-09-06 12:28:01

编辑:抱歉,原始版本不正确。固定的。

这是发生的事情。您对 remove_if 的输入是:

1  2  3  4  5  6
      ^     ^
    begin  end

remove_if 算法会查看 beginend 之间的所有数字(包括 remove_if >begin,但不包括 end),并删除与谓词匹配的所有元素。因此,在 remove_if 运行之后,您的向量看起来像这样

1  2  3  ?  5  6
      ^  ^
  begin  new_end

其中 ? 是一个我认为不是确定性的值,尽管如果保证它是任何值,那么它就是 4new_end 指向您提供的输入序列的新结尾,现在删除了匹配的元素,这是 std::remove_if 返回的内容。请注意,std::remove_if 不会触及您提供的子序列之外的任何内容。通过更扩展的示例,这可能更有意义。

假设这是您的输入:

1  2  3  4  5  6  7  8  9  10
      ^              ^
    begin           end

std::remove_if 之后,您会得到:

1  2  3  5  7  ?  ?  8  9  10
      ^        ^
    begin      new_end

考虑一下。它所做的就是从子序列中删除 4 和 6,然后将子序列中的所有内容向下移动以填充删除的元素,然后移动 end 迭代器到同一子序列的新末尾。目标是满足它生成的 (begin, new_end] 序列与 (begin, end] 您传入的子序列,但删除了您传入的 end 处或之后的任何内容,

那么您想要删除的是 。 返回的结束迭代器与您提供的原始结束迭代器之间的所有内容这些都是“垃圾”值,因此您的擦除调用实际上应该是:

ints.erase(it, ints.begin()+4);

您刚刚对 erase 的调用会删除执行删除操作的子序列末尾之外的所有内容,这不是您想要的,

使事情变得复杂的是 remove_if 。 算法实际上并不对向量调用 erase() ,也不会在任何时候更改向量的大小,它只是四处移动元素并在结束后留下一些“垃圾”元素。您要求它处理的子序列。这看起来很愚蠢,但 STL 这样做的全部原因是为了避免 doublep 带来的无效迭代器问题(并且能够在不是 STL 容器的东西上运行,比如原始数组)。

Edit: Sorry, the original version of this was incorrect. Fixed.

Here's what's going on. Your input to remove_if is:

1  2  3  4  5  6
      ^     ^
    begin  end

And the remove_if algorithm looks at all numbers between begin and end (including begin, but excluding end), and removes all elements between that match your predicate. So after remove_if runs, your vector looks like this

1  2  3  ?  5  6
      ^  ^
  begin  new_end

Where ? is a value that I don't think is deterministic, although if it's guaranteed to be anything it would be 4. And new_end, which points to the new end of the input sequence you gave it, with the matching elements now removed, is what is returned by std::remove_if. Note that std::remove_if doesn't touch anything beyond the subsequence that you gave it. This might make more sense with a more extended example.

Say that this is your input:

1  2  3  4  5  6  7  8  9  10
      ^              ^
    begin           end

After std::remove_if, you get:

1  2  3  5  7  ?  ?  8  9  10
      ^        ^
    begin      new_end

Think about this for a moment. What it has done is remove the 4 and the 6 from the subsequence, and then shift everything within the subsequence down to fill in the removed elements, and then moved the end iterator to the new end of the same subsequence. The goal is to satisfy the requirement that the (begin, new_end] sequence that it produces is the same as the (begin, end] subsequence that you passed in, but with certain elements removed. Anything at or beyond the end that you passed in is left untouched.

What you want to get rid of, then, is everything between the end iterator that was returned, and the original end iterator that you gave it. These are the ? "garbage" values. So your erase call should actually be:

ints.erase(it, ints.begin()+4);

The call to erase that you have just erases everything beyond the end of the subsequence that you performed the removal on, which isn't what you want here.

What makes this complicated is that the remove_if algorithm doesn't actually call erase() on the vector, or change the size of the vector at any point. It just shifts elements around and leaves some "garbage" elements after the end of the subsequence that you asked it to process. This seems silly, but the whole reason that the STL does it this way is to avoid the problem with invalidated iterators that doublep brought up (and to be able to run on things that aren't STL containers, like raw arrays).

长途伴 2024-09-06 12:28:01

删除 std::vector 中的元素会使删除元素之后的迭代器失效,因此您不能使用接受范围的“外部”函数。您需要以不同的方式执行此操作。

编辑:

一般来说,您可以利用这样一个事实:擦除一个元素会将其他位置的所有元素向后“移动”。像这样的事情:

for (size_t scan = 2, end = 4; scan != end; )
  {
     if (/* some predicate on ints[scan] */)
       {
         ints.erase (ints.begin () + scan);
         --end;
       }
     else
       ++scan;
  }

请注意,std::vector 不适合擦除中间的元素。如果您经常这样做,您应该考虑其他事情(std::list?)。

编辑2:

正如评论所澄清的,第一段不正确。在这种情况下, std::remove_if 应该比我在第一次编辑中建议的更有效,所以忽略这个答案。 (保留以供评论。)

Erasing elements in std::vector invalidates iterators past the removed element, so you cannot use "foreign" functions that accept ranges. You need to do that in a different way.

EDIT:

In general, you can use the fact that erasing one element "shifts" all elements at further positions one back. Something like this:

for (size_t scan = 2, end = 4; scan != end; )
  {
     if (/* some predicate on ints[scan] */)
       {
         ints.erase (ints.begin () + scan);
         --end;
       }
     else
       ++scan;
  }

Note that std::vector isn't suited for erasing elements in the middle. You should consider something else (std::list?) if you do that often.

EDIT 2:

As clarified by comments, first paragraph is not true. In such case std::remove_if should be more efficient than what I suggested in the first edit, so disregard this answer. (Keeping it for the comments.)

断肠人 2024-09-06 12:28:01

这种行为并不奇怪——你删除了错误的范围。 std::remove_if 将其“删除”的元素移动到输入范围的末尾。在这种情况下,您需要做的是:

ints.erase(it, ints.begin() + 4 /* your end of range */);

From C++ in a Nutshell:

remove_if 函数模板
“删除” pred 返回的项目
false 从范围 [第一个,最后一个)。
返回值大于新值
范围的末端。相对顺序
未删除的项目是
稳定。

实际上没有删除任何内容
底层容器;相反,项目
右边被分配给新的
位置,以便它们覆盖
pred 返回 false 的元素。
参见图13-13(在remove_copy下)
有关删除过程的示例。

The behavior isn't weird - you're erasing the wrong range. std::remove_if moves elements it "removes" to the end of the input range. In this case, what you're looking for would be to do:

ints.erase(it, ints.begin() + 4 /* your end of range */);

From C++ in a Nutshell:

The remove_if function template
"removes" items for which pred returns
false from the range [first, last).
The return value is one past the new
end of the range. The relative order
of items that are not removed is
stable.

Nothing is actually erased from the
underlying container; instead, items
to the right are assigned to new
positions so they overwrite the
elements for which pred returns false.
See Figure 13-13 (under remove_copy)
for an example of the removal process.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文