在执行std :: remove_if期间遍历容器是否安全?

时间:2019-07-16 10:22:57

标签: c++ algorithm stdvector

假设我想从std::vector中删除 unique 元素(不消除重复项,而仅保留至少出现两次的元素),并且我想实现以一种非常低效的方式-通过在std::count期间调用std::remove_if来实现。考虑以下代码:

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 6, 3, 6, 2, 7, 4, 4, 5, 6};

    auto to_remove = std::remove_if(vec.begin(), vec.end(), [&vec](int n) {
        return std::count(vec.begin(), vec.end(), n) == 1;
    });

    vec.erase(to_remove, vec.end());

    for (int i : vec) std::cout << i << ' ';
}

reference on std::remove_if中我们知道,从to_remove开始的元素具有未指定值,但是我想知道它们实际上到底有多未指定。

为进一步说明我的担忧-我们可以看到应该删除的元素为1357-唯一的值。 std::remove_if会将1移到末尾,但不能保证在执行完该操作后,末尾将有一个值1。是否可以(由于该值是 unspecified )而变成3并进行std::count调用返回(例如) 2 < / strong>为以后遇到的值3

本质上,我的问题是-这样可以保证工作吗?通过 work 我的意思是要低效率地擦除std::vector中的唯一元素?

我对语言-律师答案(可能是“ 标准说这种情况是可能的,应该避免这种情况”)和实践中的答案(可能是“ < em>该标准指出,这种情况是可能的,但实际上,这种值不可能最终成为完全不同的值,例如3 “)。

3 个答案:

答案 0 :(得分:6)

在谓词第一次返回true之后,范围中将有一个未指定的值。这意味着谓词的任何后续调用都将计入未指定的值。因此,该计数可能不正确,您可以保留不打算丢弃的值,也可以丢弃应保留的值。

您可以修改谓词,以便保留其返回true的次数,并相应地减小范围。例如;

std::size_t count = 0;
auto to_remove = std::remove_if(vec.begin(), vec.end(), [&vec, &count](int n)
{
    bool once = (std::count(vec.begin(), vec.end() - count, n) == 1);
    if (once) ++count;
    return once;
 });

从向量的结束迭代器中减去整数值是安全的,但对于其他容器却不一定如此。

答案 1 :(得分:5)

您误解了std::remove_if的工作方式。要删除的值不一定要移到最后。参见:

  

删除是通过移动(通过移动分配)范围内的元素来完成的,要删除的元素出现在范围的开头。 cppreference

这是范围状态的唯一保证。据我所知,并不是禁止将所有值转移,它仍然可以满足复杂性。因此,某些编译器可能会将不需要的值移到最后,但这只是多余的工作。

1 2 3 4 8 5中删除奇数的可能实现示例:

   v               - read position
   1 2 3 4 8 5     - X will denotes shifted from value = unspecified
   ^               - write position
     v          
   1 2 3 4 8 5     1 is odd, ++read
   ^
       v
   2 X 3 4 8 5     2 is even, *write=move(*read), ++both
     ^   
         v
   2 X 3 4 8 5     3 is odd, ++read
     ^
           v
   2 4 3 X 8 5     4 is even, *write=move(*read), ++both
       ^
             v
   2 4 8 X X 5     8 is even, *write=move(*read), ++both
         ^

   2 4 8 X X 5     5 is odd, ++read
         ^         - this points to the new end.

因此,通常来说,您不能依靠count返回任何有意义的值。因为在move == copy的情况下(对于ints而言),结果数组为2 4 8|4 8 5。奇数和偶数的计数都不正确。如果是std::unique_ptr,则X==nullptr以及nullptr的计数和删除的值可能是错误的。其他剩余值不应留在数组的末尾,因为没有完成复制。

请注意,这些值不是未指定的,因为您无法知道它们。它们正是移动分配的结果,可能会使值保持未指定状态。如果它指定了移出变量的状态(如std::unique_ptr那样),则它们将是已知的。例如。如果move==swap,则范围将仅被排列。

答案 2 :(得分:1)

我添加了一些输出:

#include <algorithm>
#include <iostream>
#include <vector>
#include <mutex>

int main() {
    std::vector<int> vec = {1, 2, 6, 3, 6, 2, 7, 4, 4, 5, 6};

    auto to_remove = std::remove_if(vec.begin(), vec.end(), [&vec](int n) {

        std::cout << "number " << n << ": ";
        for (auto i : vec) std::cout << i << ' ';
        auto c = std::count(vec.begin(), vec.end(), n);
        std::cout << ", count: " << c << std::endl;
        return c == 1;
    });

    vec.erase(to_remove, vec.end());

    for (int i : vec) std::cout << i << ' ';
}

得到

number 1: 1 2 6 3 6 2 7 4 4 5 6 , count: 1
number 2: 1 2 6 3 6 2 7 4 4 5 6 , count: 2
number 6: 2 2 6 3 6 2 7 4 4 5 6 , count: 3
number 3: 2 6 6 3 6 2 7 4 4 5 6 , count: 1
number 6: 2 6 6 3 6 2 7 4 4 5 6 , count: 4
number 2: 2 6 6 3 6 2 7 4 4 5 6 , count: 2
number 7: 2 6 6 2 6 2 7 4 4 5 6 , count: 1
number 4: 2 6 6 2 6 2 7 4 4 5 6 , count: 2
number 4: 2 6 6 2 4 2 7 4 4 5 6 , count: 3
number 5: 2 6 6 2 4 4 7 4 4 5 6 , count: 1
number 6: 2 6 6 2 4 4 7 4 4 5 6 , count: 3
2 6 6 2 4 4 6 

如您所见,计数可能是错误的。我无法为您的特殊情况创建示例,但通常您必须担心错误的结果。

首先对数字4进行两次计数,然后在下一步中对数字4进行三次计数。计数是错误的,您不能依靠它们。