2 votes

Comment nettoyer correctement les éléments des vecteurs de pointeurs d'objets ?

J'ai étudié l'allocation dynamique et je suis tombé sur cette question sur StackOverflow :

Désallocation d'objets stockés dans un vecteur ?

L'une des réponses votées explique comment gérer manuellement la mémoire lors de l'utilisation de "Vecteurs de pointeurs vers des objets" : itérer à travers le vecteur en appelant delete.

Ma question porte sur la manière de supprimer des éléments particuliers d'un vecteur, et non le vecteur entier. Dans l'exemple de programme ci-dessous, j'ai un vecteur de pointeurs d'objets. Imaginez que la variable x de ces objets soit décrémentée au fil du temps... Lorsque la valeur x d'un objet atteint un certain nombre (disons 3), je souhaite supprimer l'objet ; CEPENDANT, j'aimerais que le vecteur puisse toujours être trié en fonction des valeurs x des objets.

Le problème est que lorsque j'appelle delete sur les objets dont les valeurs x atteignent 3, l'objet est supprimé mais il y a toujours un pointeur qui pointe vers des emplacements de mémoire aléatoires, et la taille du vecteur reste également la même.

Lorsque je boucle sur le vecteur en imprimant les valeurs x, les éléments sur lesquels j'ai appelé delete sont toujours là mais pointent vers des valeurs comme -53408995. Comment puis-je me débarrasser de l'élément pointeur du vecteur ainsi que de l'objet ?

Appeler erase n'est pas une option car dans mon programme réel (pas l'exemple minimal ci-dessous) le vecteur est continuellement TRIE par d'autres facteurs qui changent les équivalents des valeurs x. Je ne peux pas garder trace de leur index. Je voudrais supprimer à la fois l'objet et l'élément pointeur lorsque j'itère à travers le vecteur pour vérifier la valeur x.

Exemple :

#include <iostream>
#include <vector>

class A
{
public:
    A(int i) { x = i; }
    int x;
};

int main()
{
    std::vector<A*> Vec;

    Vec.push_back(new A{ 5 });
    Vec.push_back(new A{ 4 });
    Vec.push_back(new A{ 3 });

    std::cout << "Size before = " << Vec.size() << std::endl; // 3

    for (auto& a : Vec)
    {
        std::cout << a->x << std::endl;

        if (a->x == 3) { delete a; }
    }

    std::cout << "Size after = " << Vec.size() << std::endl; // Still 3!

    for (auto& a : Vec)
    {
        std::cout << a->x << std::endl; // Prints 5, 4 and a random memory location like -34528374
    }

    return 0;
}

3voto

phön Points 526

Vous avez mentionné dans les commentaires que les objets auront d'autres pointeurs vers eux. cela ressemble à std::shared_ptr<A> de cette façon, vous pouvez avoir d'autres pointeurs vers votre A sans problème de fuite de mémoire. ( std::shared_ptr s'accompagne d'un faible coût ( !) en termes de performances, mais vous ne devriez pas vous en préoccuper pour l'instant). De plus, j'ai modifié votre passage pour supprimer/effacer votre élément du vecteur. soyez conscient que la fonction A sont encore en vie SI d'autres instances conservent un objet std::shared_ptr<A> (mais c'est une bonne chose).

Voici le code :

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

class A
{
public:
    A(int i) { x = i; }
    int x;
};

int main()
{
    std::vector<std::shared_ptr<A>> Vec;
    Vec.emplace_back(std::make_shared<A>(5));
    Vec.emplace_back(std::make_shared<A>(4));
    Vec.emplace_back(std::make_shared<A>(3));

    std::cout << "Size before = " << Vec.size() << std::endl;

    Vec.erase(
        std::remove_if(std::begin(Vec),std::end(Vec), [](auto&& ptr){ return ptr->x == 3;}),
        std::end(Vec));

    std::cout << "Size after = " << Vec.size() << std::endl;
    for (auto&& a : Vec)
    {
        std::cout << a->x << std::endl;
    }

    return 0;
}

2voto

marouane18 Points 105

Dans ce cas, vous devez utiliser un std::list conteneur

#include <iostream>
#include <list>

class A
{
public:
    A(int i) { x = i; }
    int x;
};

int main()
{
    std::list<A*> Vec;

    Vec.push_back(new A{ 5 });
    Vec.push_back(new A{ 4 });
    Vec.push_back(new A{ 3 });

    std::cout << "Size before = " << Vec.size() << std::endl;

    for (auto& a : Vec)
    {
        std::cout << a->x << std::endl;
    }

    Vec.remove_if([](A* a){
        bool status = (a->x == 3);
        if(status){
            delete a;
            return true;
        }else{
            return false;
        }
    });

    std::cout << "Size after = " << Vec.size() << std::endl;

    for (auto& a : Vec)
    {
        std::cout << a->x << std::endl;
    }

    return 0;
}

sortie :

Size before = 3
5
4
3
Size after = 2
5
4

Je réécris votre code en y apportant quelques améliorations

#include <iostream>
#include <list>

class A
{
public:
    A(const int& i):x(i) {} // so X is assigned to i in construction ( more efficient )
    int get_x() const {return x;}
private:
    int x; // x have to be private ( good practice )
};

int main()
{
    std::list<A> List; // A instead of A* so the process of construction / destruction is handled automatically

    List.emplace_back(5); // append element and constructed at the same time
    List.emplace_back(4); // see std::list for more details
    List.emplace_back(3);

    std::cout << "Size before = " << List.size() << std::endl;

    for (auto& a : List)
    {
        std::cout << a.get_x() << std::endl;
    }

    List.remove_if([](A a){ return a.get_x() == 3;});

    std::cout << "Size after = " << List.size() << std::endl;

    for (auto& a : List)
    {
        std::cout << a.get_x() << std::endl;
    }
    return 0;
}

1voto

Archie Yalakki Points 191
std::vector<std::unique_ptr<A>> vec;

Cela permettrait de gérer la suppression normale et de sortir de l'exception.

0voto

Nathaniel G.M. Points 126

Après avoir étudié un peu les itérateurs, j'ai également trouvé une réponse qui utilise des pointeurs bruts :

for (std::vector<A*>::iterator it = Vec1.begin(); it != Vec1.end(); )
{
    if ((*it)->x == 3)
    {
        delete * it;
        it = Vec1.erase(it);
    }
    else 
    {
        ++it;
    }
}

Je conserverai le message de Phön comme réponse, car les pointeurs intelligents devraient toujours être préférés aux pointeurs bruts s'ils sont disponibles.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X