web-dev-qa-db-fra.com

Std :: list :: remove est-il une méthode qui appelle un destructeur de chaque élément supprimé?

std::list<Node *> lst;
//....
Node * node = /* get from somewhere pointer on my node */;
lst.remove(node);

La méthode std :: list :: remove appelle-t-elle le destructeur (et la mémoire libre) de chaque élément supprimé? Si oui, comment puis-je l'éviter?

26
Siarhei Fedartsou

Oui, le retrait d'un Foo* d'un conteneur détruit le Foo*, mais il ne libérera pas le Foo. Détruire un pointeur brut est toujours un non-op. Il ne peut en être autrement! Laissez-moi vous donner plusieurs raisons pour lesquelles.

Classe de stockage

La suppression d'un pointeur n'a de sens que si la pointee a été réellement allouée dynamiquement, mais comment le moteur d'exécution pourrait-il éventuellement savoir s'il en est ainsi lorsque la variable pointeur est détruite? Les pointeurs peuvent également pointer sur des variables statiques et automatiques et supprimer un de ces rendements comportement non défini .

{
    Foo x;
    Foo* p = &x;

    Foo* q = new Foo;

    // Has *q been allocated dynamically?
    // (The answer is YES, but the runtime doesn't know that.)

    // Has *p been allocated dynamically?
    // (The answer is NO, but the runtime doesn't know that.)
}

Pointeurs pendants

Il n'y a aucun moyen de savoir si la pointee a déjà été publiée dans le passé. Si vous supprimez deux fois le même pointeur, vous obtenez un comportement non défini . (Il devient un pointeur en suspens après la première suppression.)

{
    Foo* p = new Foo;

    Foo* q = p;

    // Has *q already been released?
    // (The answer is NO, but the runtime doesn't know that.)

    // (...suppose that pointees WOULD be automatically released...)

    // Has *p already been released?
    // (The answer WOULD now be YES, but the runtime doesn't know that.)
}

Pointeurs non initialisés

Il est également impossible de détecter si une variable de pointeur a été initialisée. Devinez ce qui se passe lorsque vous essayez de supprimer un tel pointeur? Encore une fois, la réponse est comportement indéfini .

    {
        Foo* p;

        // Has p been properly initialized?
        // (The answer is NO, but the runtime doesn't know that.)
    }

Tableaux dynamiques

Le système de types ne fait pas la distinction entre un pointeur sur un objet unique (Foo*) et un pointeur sur le premier élément d'un tableau d'objets (également Foo*). Quand une variable de pointeur est détruite, le moteur d’exécution ne peut déterminer s'il faut relâcher la pointee via delete ou via delete[]. Le relâchement via un formulaire incorrect appelle un comportement non défini .

{
    Foo* p = new Foo;

    Foo* q = new Foo[100];

    // What should I do, delete q or delete[] q?
    // (The answer is delete[] q, but the runtime doesn't know that.)

    // What should I do, delete p or delete[] p?
    // (The answer is delete p, but the runtime doesn't know that.)
}

Résumé

Comme le runtime ne peut rien faire de raisonnable avec la pointee, détruire une variable de pointeur revient à toujours un non-op. Ne rien faire est définitivement préférable à un comportement indéfini dû à une supposition non informée :-)

Conseil

Au lieu d'utiliser des pointeurs bruts, envisagez d'utiliser des pointeurs intelligents comme type de valeur de votre conteneur, car ils prennent la responsabilité de libérer la pointee lorsqu'elle n'est plus nécessaire. En fonction de vos besoins, utilisez std::shared_ptr<Foo> ou std::unique_ptr<Foo>. Si votre compilateur ne supporte pas encore C++ 0x, utilisez boost::shared_ptr<Foo>.

Jamais , je répète, JAMAIS JAMAIS utilisez std::auto_ptr<Foo> comme type de valeur d'un conteneur.

43
fredoverflow

Il appelle le destructeur de chacun des éléments de la list - mais ce n'est pas un objet Node. C'est un Node*.

Donc, il ne supprime pas les pointeurs Node.

Cela a-t-il du sens?

12
John Dibling

Il appelle le destructeur des données de la liste. Cela signifie que std::list<T>::remove appellera le destructeur de T (ce qui est nécessaire lorsque T est quelque chose comme std::vector).

Dans votre cas, il appellerait le destructeur de Node*, qui est un no-op. Il n'appelle pas le destructeur de node.

7
jpalecek

Oui, bien que dans ce cas, Node * n'ait pas de destructeur. Cependant, en fonction de ses composants internes, les différentes valeurs de Node * sont soit supprimées, soit détruites par des règles de portée. Si Node * était un type non fondamental, un destructeur serait appelé.

Le destructeur est-il appelé sur le nœud? Non, mais 'Node' n'est pas le type d'élément dans la liste.

Quant à votre autre question, vous ne pouvez pas. Le conteneur de liste standard (en fait, TOUS les conteneurs standard) devient propriétaire de son contenu et le nettoie. Si vous ne voulez pas que cela se produise, les conteneurs standard ne constituent pas un bon choix.

3
Crazy Eddie

La meilleure façon de comprendre est de tester chaque formulaire et d'observer les résultats. Pour utiliser habilement les objets conteneur avec vos propres objets personnalisés, vous devez bien comprendre le comportement.

En bref, pour le type Node* ni le deconstructor n’est appelé, ni delete/free n’est appelé; toutefois, pour le type Node, le déconstructeur serait invoqué, tandis que la suppression ou la suppression est un détail de la mise en oeuvre de la liste. Cela signifie que cela dépend si l’implémentation de la liste utilise new/malloc.

Dans le cas d'un unique_ptr<Node>, le déconstructeur est appelé et l'appel de delete/free aura lieu puisque vous devez lui donner quelque chose alloué par new.

#include <iostream>
#include <list>
#include <memory>

using namespace std;

void* operator new(size_t size) {
    cout << "new operator with size " << size << endl;
    return malloc(size);
}

void operator delete(void *ptr) {
    cout << "delete operator for " << ptr << endl;
    free(ptr);
}

class Apple {
public:
    int id;

    Apple() : id(0) { cout << "Apple " << this << ":" << this->id << " constructed" << endl; } 
    Apple(int id) : id(id) { cout << "Apple " << this << ":" << this->id << " constructed" << endl; }
    ~Apple() { cout << "Apple " << this << ":" << this->id << " deconstructed" << endl; }

    bool operator==(const Apple &right) {
        return this->id == right.id;
    }

    static void* operator new(size_t size) {
        cout << "new was called for Apple" << endl;
        return malloc(size);
    }

    static void operator delete(void *ptr) {
        cout << "delete was called for Apple" << endl;
        free(ptr);
    }
    /*
        The compiler generates one of these and simply assignments
        member variable. Think memcpy. It can be disabled by uncommenting
        the below requiring the usage of std::move or one can be implemented.
    */
    //Apple& operator=(const Apple &from) = delete;
};

int main() {
    list<Apple*> a = list<Apple*>();

    /* deconstructor not called */
    /* memory not released using delete */
    cout << "test 1" << endl;
    a.Push_back(new Apple());
    a.pop_back();

    /* deconstructor not called */
    /* memory not released using delete */
    cout << "test 2" << endl;
    Apple *b = new Apple();
    a.Push_back(b);
    a.remove(b);
    cout << "list size is now " << a.size() << endl;

    list<Apple> c = list<Apple>();      
    cout << "test 3" << endl;
    c.Push_back(Apple(1)); /* deconstructed after copy by value (memcpy like) */
    c.Push_back(Apple(2)); /* deconstructed after copy by value (memcpy like) */

    /*
       the list implementation will call new... but not
       call constructor when Apple(2) is pushed; however,
       delete will be called; since it was copied by value
       in the last Push_back call

       double deconstructor on object with same data
    */
    c.pop_back();

    Apple z(10);

    /* will remove nothing */
    c.remove(z);

    cout << "test 4" << endl;

    /* Apple(5) will never deconstruct. It was literally overwritten by Apple(1). */
    /* Think memcpy... but not exactly. */
    z = Apple(1);

    /* will remove by matching using the operator== of Apple or default operator== */
    c.remove(z);

    cout << "test 5" << endl;
    list<unique_ptr<Apple>> d = list<unique_ptr<Apple>>();
    d.Push_back(unique_ptr<Apple>(new Apple()));
    d.pop_back();

    /* z deconstructs */
    return 0;
}

Faites très attention aux adresses de la mémoire. Vous pouvez déterminer ceux qui pointent dans la pile et ceux qui pointent dans le tas en fonction des plages.

0
kmcguire

Comme vous placez des pointeurs dans un std::list, les destructeurs ne sont pas appelés sur les objets pointés à Node.

Si vous souhaitez stocker des objets alloués aux segments de mémoire dans des conteneurs STL et les faire détruire lors de leur suppression, placez-les dans un pointeur intelligent tel que boost::shared_ptr

0
wkl