【问题标题】:iterate vector, remove certain items as I go迭代向量,边走边删除某些项目
【发布时间】:2010-12-08 22:36:47
【问题描述】:

我有一个 std::vector m_vPaths;我将迭代这个向量并调用 ::DeleteFile(strPath) 。如果我成功删除文件,我会将其从向量中删除。我的问题是我可以避免使用两个向量吗?是否有不同的数据结构可能更适合我需要做的事情?

示例: 使用迭代器几乎可以满足我的要求,但问题是一旦使用迭代器擦除,所有迭代器都会失效。

 std::vector<std::string> iter = m_vPaths.begin();
    for( ; iter != m_vPaths.end(); iter++) {
        std::string strPath = *iter;
        if(::DeleteFile(strPath.c_str())) {
            m_vPaths.erase(iter);   
                //Now my interators are invalid because I used erase,
                //but I want to continue deleteing the files remaining in my vector.    
        }
    }

我可以使用两个向量,我将不再有问题,但是有没有更好、更有效的方法来做我想做的事情?

顺便说一句,如果不清楚,m_vPaths 是这样声明的(在我的班级中):

std::vector<std::string> m_vPaths;

【问题讨论】:

  • 另外,我真的不知道它使用什么样的数据结构,如果有比向量更好的东西,请告诉我。我不认为 std::queue 或 std::list 有任何可以帮助我的东西(虽然我可能是错的:)

标签: c++ data-structures vector loops iterator


【解决方案1】:

erase() 方法返回一个新的(有效的)迭代器,它指向被删除元素之后的下一个元素。您可以使用此迭代器继续循环:

std::vector<std::string>::iterator iter;
for (iter = m_vPaths.begin(); iter != m_vPaths.end(); ) {
    if (::DeleteFile(iter->c_str()))
        iter = m_vPaths.erase(iter);
    else
        ++iter;
}

【讨论】:

  • 微妙。我几乎对此投了反对票,因为我认为擦除会使以下迭代器无效。感谢您的链接。
  • 但是,这种方法不适用于所有 STL 容器(尤其是 std::map),而擦除(范围)/删除组合适用于任何 STL 容器。
  • 对于std::map,您可以使用erase(iter++);,因为erase 不会使其他迭代器无效,而不是被删除的迭代器。
  • 将 '++iter' 作为正常的 foo 循环的第三个表达式实际上是不正确的吗?如果 n-1 被擦除,这样做似乎会导致它跳过元素 n。
  • @averagejoe:如果这样做,erase() 返回的迭代器会在下一次循环迭代中递增。但是您不希望它增加 - erase() 为下一次循环迭代返回了正确的迭代器,另外增加它会跳过一个元素。
【解决方案2】:

查看std::remove_if:

#include <algorithm> // for remove_if
#include <functional> // for unary_function

struct delete_file : public std::unary_function<const std::string&, bool> 
{
    bool operator()(const std::string& strPath) const
    {
        return ::DeleteFile(strPath.c_str());
    }
}

m_vPaths.erase(std::remove_if(m_vPaths.begin(), m_vPaths.end(), delete_file()),
                m_vPaths.end());

使用std::list 来阻止无效迭代器问题,尽管您会失去随机访问权。 (以及一般的缓存性能)


为了记录,您实现代码的方式是:

typedef std::vector<std::string> string_vector;
typedef std::vector<std::string>::iterator string_vector_iterator;

string_vector_iterator iter = m_vPaths.begin();
while (iter != m_vPaths.end())
{
    if(::DeleteFile(iter->c_str()))
    {
        // erase returns the new iterator
        iter = m_vPaths.erase(iter);
    }
    else
    {
        ++iter;
    }
}

但你应该使用std::remove_if(重新发明轮子是不好的)。

【讨论】:

    【解决方案3】:

    考虑到擦除文件的时间,这可能并不重要,但我仍然建议向后迭代向量 - 这样您通常会从(接近)向量末尾删除项目。删除一个项目所花费的时间与向量中跟随它的项目数成正比。如果(例如)您有一个包含 100 个文件名的向量,并且您成功地删除了所有文件名,那么您将在此过程中复制最后一个元素 100 次(并将倒数第二个元素复制 99 次,依此类推)。

    OTOH,如果您从头开始并向后工作,只要删除文件成功,您就不会复制。您可以使用反向迭代器向后遍历向量,而无需更改任何其他内容。例如,使用 remove_if 的 GMan 代码应该可以继续工作(只是快一点),只需用 rbegin() 代替 begin(),用 rend() 代替 end。

    另一种可能性是使用双端队列而不是向量——双端队列可以在恒定时间内从集合的末尾开始擦除项目。

    【讨论】:

    • 将第n个好项目移动到第n个索引不是更好吗?我们只需要索引位置值,一个用于第 n 个好的项目位置,一个用于第 n 个索引。这将确保所有好的项目都向前推进,一旦完成,我们可以将向量调整为 n 个元素(不确定是否支持)。最多在要删除第一个元素时发生 n-1 交换。
    猜你喜欢
    • 2021-11-08
    • 2019-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多