在迭代过程中从集合中删除的最有效方法是什么?以下是我想到的两种方法,它们之间最好的方法是什么?还有另一种更好的方法吗?
void WaitForFiles(std::set<string> files) {
while (files.size() > 0) {
std::set<string> found_files;
for (const auto& file : files) {
if (Exists(file)) {
found_files.insert(file);
}
}
for (const auto& found_file : found_files) {
files.erase(file);
}
}
}
使用set_difference:
void WaitForFiles(std::set<string> files) {
while (files.size() > 0) {
std::set<string> found_files;
for (const auto& file : files) {
if (Exists(file)) {
found_files.insert(file);
}
}
std::set<string> difference;
std::set_difference(files.begin(), files.end(),
found_files.begin(), found_files.end(),
std::inserter(difference, difference.end()));
files = difference;
}
}
请注意以下崩溃:
void WaitForFiles(std::set<string> files) {
while (files.size() > 0) {
for (const auto& file : files) { // <-- seg fault after first erase
if (Exists(file)) {
files.erase(file);
}
}
}
}
为了确定效率,请记住,在我的情况下,文件可能需要30分钟才能生成,因此Exists函数将被多次调用,并且与文件集的次数相比,文件集不会经常更改循环迭代。
答案 0 :(得分:12)
从基于范围的for循环中的集合中擦除是未定义的行为(即使它看起来有效)。基于范围的for循环在内部使用迭代器,并且擦除元素使迭代器无效。
但std::set::erase
将有效迭代器返回到std::set
中的下一个元素,因此您可以使用显式迭代器循环:
for(auto itr = files.cbegin(); itr != files.cend();) {
if (exists(*itr)) {
std::cout << "Found file: " << *itr << "\n";
itr = files.erase(itr);
} else
++itr;
}
答案 1 :(得分:4)
来自库基础知识TS的v2的std::experimental::erase_if
:
std::experimental::erase_if(files, Exists);
如果Exists
重载或是函数模板,请使用lambda:
std::experimental::erase_if(files, [](const auto& f) { return Exists(f); });
这已在VS2015中实现。
答案 2 :(得分:1)
前两个示例看起来不是最理想的,因为它们都涉及遍历 set 两次。您的第三个示例是不稳定的,因为在使用std::set::erase
更改集合使其失效后继续使用迭代器。
我认为以下内容应该合理有效。但是,正如其他人提到的那样,将std::set<std::string> found_files
替换为std::vector
可能是值得的。
#include <set>
#include <string>
#include <iostream>
/**
* Return true for every other call
*/
bool Exists(std::string const&)
{
static int i = 0;
return ++i % 2;
}
std::set<std::string> find_existing_files(std::set<std::string>& files)
{
std::set<std::string> found_files;
for(auto file = files.begin(); file != files.end();)
{
if(!Exists(*file))
++file; // not erasing, keep going
else
{
found_files.insert(*file);
// replacing iterator with result of erase()
// keeps the iterator valid
file = files.erase(file);
}
}
return found_files;
}
int main()
{
std::set<std::string> files {"a", "b", "c", "d", "e"};
for(auto const& file: find_existing_files(files))
std::cout << "exists : " << file << '\n';
for(auto const& file: files)
std::cout << "missing: " << file << '\n';
}
<强>输出:强>
exists : a
exists : c
exists : e
missing: b
missing: d