在 std::remove_if 执行期间遍历容器是否安全?
Is it safe to traverse a container during std::remove_if execution?
>假设我想从std::vector
中删除唯一元素(不删除重复项,但只保留至少出现 2 次的元素),我想以一种非常低效的方式实现这一目标 - 通过在std::remove_if
ing 时调用std::count
。请考虑以下代码:
#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 << ' ';
}
从std::remove_if
参考文献中我们知道从to_remove
开始的元素具有未指定的值,但我想知道它们实际上有多未指定。
为了进一步解释我的担忧 - 我们可以看到应该删除的元素是1
、3
、5
和7
- 唯一的唯一值。std::remove_if
会将1
移到末尾,但不能保证在上述操作后最后会有一个值1
。这是否可以(由于该值未指定)变成3
并使std::count
调用为以后遇到的值返回计数(例如)23
?
从本质上讲,我的问题是 - 这是否保证有效,我所说的工作是指从std::vector
中低效地擦除独特的元素?
我对语言律师的回答(可能是"标准说这种情况是可能的,你应该避免它")和实践中的答案(可能是">标准说这种情况是可能的,但实际上没有办法让这个值最终成为一个完全不同的值,例如3
")。
谓词第一次返回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;
});
从向量的结束迭代器中减去整数值是安全的,但对于其他容器来说不一定如此。
你误解了std::remove_if
是如何工作的。要删除的值不一定移到末尾。看:
不要删除的元素出现在范围的开头。 CPP 首选项
这是范围状态的唯一保证。据我所知,不禁止移动所有值,它仍然可以满足复杂性。因此,某些编译器可能会将不需要的值移到末尾,但这只是额外的不必要的工作。
从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
则范围将仅排列。
我添加了一些输出:
#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 计数三次。计数是错误的,你不能依赖它们。
- 通过网络、跨平台传递std::变体是否安全
- 在类型和包装器之间reinterpret_cast是否安全<Type>?
- 跨 DLL 边界访问虚拟方法是否安全/可能?
- 静态 constexpr 类成员变量对多线程读取是否安全?
- 在函数结束后使用指向变量的指针是否安全?
- 逐字节删除 void* 是否安全?
- 在 RAII 构造中修改 RVO 值是否安全?
- 线程调用的函数对对象删除是否安全?
- 将对象的字节复制到数组并再次复制回来是否安全
- 使用枚举为数组编制索引是否安全?
- 返回从字符串文本创建的静态string_view是否安全?
- 在cstlib中将#include_next替换为#include是否安全
- 由并发无序映射查找线程调用的函数是否安全?
- 使用 c++ 原子时编写"y=++x"是否安全?
- 从另一个线程发出信号是否安全?
- 从其存储的回调中删除 std::函数是否安全
- 使用 std::vector::swap 方法在C++中交换两个不同的向量是否安全?
- 当我在C++中调用 struce 的只读静态成员时,线程是否安全
- 同时调用 ASIO 对象的 API 是否安全?
- 使用自己的迭代器分配容器是否安全?