web-dev-qa-db-fra.com

itérer le vecteur, supprimer certains éléments au fur et à mesure

J'ai un std :: vector m_vPaths; Je vais itérer ce vecteur et appeler :: DeleteFile (strPath) au fur et à mesure. Si je réussis à supprimer le fichier, je le supprimerai du vecteur. Ma question est la suivante: puis-je me déplacer en utilisant deux vecteurs? Existe-t-il une structure de données différente qui pourrait être mieux adaptée à ce que je dois faire?

exemple: utiliser des itérateurs fait presque ce que je veux, mais le problème est une fois que vous effacez en utilisant un itérateur, tous les itérateurs deviennent invalides.

 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.    
        }
    }

Je peux utiliser deux vecteurs et je n'aurai plus de problème, mais existe-t-il une méthode meilleure et plus efficace pour faire ce que j'essaie de faire?

btw, au cas où ce n'est pas clair, m_vPaths est déclaré comme ceci (dans ma classe):

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

Check-out 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());

Utiliser un std::list pour arrêter le problème des itérateurs invalides, bien que vous perdiez l'accès aléatoire. (Et les performances du cache, en général)


Pour mémoire, la façon dont vous implémenteriez votre code serait:

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;
    }
}

Mais vous devez utiliser std::remove_if (réinventer la roue est mauvais).

75
GManNickG

La méthode erase() renvoie un nouvel itérateur (valide) qui pointe vers l'élément suivant après celui supprimé. Vous pouvez utiliser cet itérateur pour continuer la boucle:

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;
}
117
sth

Compte tenu du temps nécessaire pour effacer un fichier, cela n'a probablement pas d'importance, mais je conseillerais quand même d'itérer à travers le vecteur vers l'arrière - de cette façon, vous supprimez normalement les éléments de (près de) la fin du vecteur. Le temps nécessaire pour supprimer un élément est proportionnel au nombre d'éléments le suivant dans le vecteur. Si (par exemple) vous disposez d'un vecteur de 100 noms de fichier et que vous les supprimez tous avec succès, vous copiez le dernier élément 100 fois dans le processus (et copiez l'avant-dernier élément 99 fois, etc.).

OTOH, si vous commencez par la fin et travaillez en arrière, vous ne copiez pas tant que la suppression des fichiers a réussi. Vous pouvez utiliser des itérateurs inversés pour parcourir le vecteur en arrière sans changer grand chose d'autre. Par exemple, le code de GMan utilisant remove_if devrait continuer à fonctionner (seulement un peu plus vite) simplement en substituant rbegin () à begin () et rend () à end.

Une autre possibilité consiste à utiliser un deque au lieu d'un vecteur - un deque peut effacer des éléments de la fin o le début de la collection en temps constant.

7
Jerry Coffin