144 votes

Quand appelle-t-on un destructeur C ++?

Question de base: quand un appel de programme de la classe destructeur méthode en C++? J'ai été informé qu'il est appelé à chaque fois qu'un objet est hors de portée ou est soumis à un delete

Des questions plus spécifiques:

1) Si l'objet est créé par l'intermédiaire d'un pointeur et que le pointeur est ensuite supprimé ou donné une nouvelle adresse, l'objet qu'il pointait à l'appel de son destructeur (en supposant que rien d'autre n'est pointant vers celui-ci)?

2) Suite à la question 1, ce qui définit lorsqu'un objet est hors de portée (pas sur de quand un objet laisse une donnée {bloc}). Donc, en d'autres termes, lorsque est un destructeur appelée sur un objet dans une liste chaînée?

3) auriez-vous jamais eu envie d'appeler un destructeur manuellement?

85voto

David Schwartz Points 70129

1) Si l'objet est créé par l'intermédiaire d'un pointeur et que le pointeur est ensuite supprimé ou donné une nouvelle adresse, l'objet qu'il pointait à l'appel de son destructeur (en supposant que rien d'autre n'est pointant vers celui-ci)?

Cela dépend du type de pointeurs. Par exemple, les pointeurs intelligents souvent de supprimer leurs objets lorsqu'ils sont supprimés. Ordinaire, les pointeurs ne sont pas. Le même est vrai si un pointeur est fait pour pointer vers un objet différent. Certains pointeurs intelligents va détruire l'ancien objet, ou va la détruire si elle n'a pas plus de références. Ordinaire des pointeurs n'ont pas cette intelligence. Ils tiennent une adresse et vous permettent d'effectuer des opérations sur les objets qu'ils point à en faire.

2) Suite à la question 1, ce qui définit lorsqu'un objet est hors de portée (pas sur de quand un objet laisse une donnée {bloc}). Donc, en d'autres termes, lorsque est un destructeur appelée sur un objet dans une liste chaînée?

Jusqu'à la mise en œuvre de la liste chaînée. Typique des collections de détruire tous les objets qu'ils contiennent, lorsqu'ils sont détruits.

Ainsi, une liste de pointeurs généralement de détruire les pointeurs, mais pas les objets qu'ils pointent. (Qui peut être correct. Ils peuvent être des références par d'autres pointeurs.) Une liste liée spécifiquement conçus pour contenir des pointeurs, cependant, peut-être supprimer les objets de sa propre destruction.

Une liste de pointeurs intelligents pourrait supprimer automatiquement les objets lorsque les pointeurs sont supprimés, ou de le faire s'ils n'avaient pas plus de références. C'est à vous de choisir les morceaux que faire ce que vous voulez.

3) auriez-vous jamais eu envie d'appeler un destructeur manuellement?

Assurez-vous. Un exemple serait si vous souhaitez remplacer un objet avec un autre objet du même type, mais ne veulent pas pour libérer de la mémoire juste pour allouer de nouveau. Vous pouvez détruire l'ancien objet en place et de construire un nouveau en place. (Cependant, généralement, c'est une mauvaise idée.)

// pointer is destroyed because it goes out of scope,
// but not the object it pointed to. memory leak
if (1) {
 Foo *myfoo = new Foo("foo");
}


// pointer is destroyed because it goes out of scope,
// object it points to is deleted. no memory leak
if(1) {
 Foo *myfoo = new Foo("foo");
 delete myfoo;
}

// no memory leak, object goes out of scope
if(1) {
 Foo myfoo("foo");
}

27voto

Jerry Coffin Points 237758

D'autres ont déjà abordé les autres questions, je vais donc il suffit de regarder un point: ne jamais vous voulez supprimer manuellement un objet.

La réponse est oui. @DavidSchwartz a donné un exemple, mais c'est assez inhabituel. Je vais vous donner un exemple qui est sous le capot de ce que beaucoup de programmeurs C++ utilise tout le temps: std::vector (et std::deque, si elle n'est pas utilisée tout autant).

Comme la plupart des gens savent, std::vector va allouer un plus grand bloc de mémoire quand/si vous ajoutez plus d'éléments que de sa répartition actuelle. Quand il le fait, cependant, il a un bloc de mémoire qui est capable de tenir plus d' objets que ceux qui sont actuellement dans le vecteur.

À gérer que ce qu' vector n'sous les couvertures est d'allouer brut de la mémoire via l' Allocator objet (qui, sauf indication contraire, les moyens qu'elle utilise ::operator new). Puis, lorsque vous utilisez (par exemple) push_back pour ajouter un élément à l' vector, en interne, le vecteur utilise un placement new de créer un élément dans la (déjà) la partie inutilisée de son espace mémoire.

Maintenant, ce qui arrive quand/si vous erase d'un élément dans le vecteur? Il ne peut pas utiliser delete -- qui aurait communiqué son bloc entier de la mémoire; il a besoin de détruire un objet en mémoire, sans détruire les autres, ou la libération de tout le bloc de mémoire qu'elle contrôle (par exemple, si vous erase 5 articles à partir d'un vecteur, puis immédiatement push_back 5 plus d'articles, il est garanti que le vecteur sera pas réallouer la mémoire lorsque vous le faites.

Pour ce faire, le vecteur directement détruit les objets dans la mémoire en appelant explicitement le destructeur, pas en utilisant delete.

Si, d'aventure, quelqu'un d'autre l'ont été pour écrire un conteneur à l'aide de stockage contigu à peu près comme un vector (ou une variante de cela, comme std::deque vraiment), vous pourriez presque certainement envie d'utiliser la même technique.

Juste pour exemple, considérons la façon dont vous pouvez écrire du code pour un anneau circulaire-tampon.

#ifndef CBUFFER_H_INC
#define CBUFFER_H_INC

template <class T>
class circular_buffer {
    T *data;
    unsigned read_pos;
    unsigned write_pos;
    unsigned in_use;
    const unsigned capacity;
public:
    circular_buffer(unsigned size) :
        data((T *)operator new(size * sizeof(T))),
        read_pos(0),
        write_pos(0),
        in_use(0),
        capacity(size)
    {}

    void push(T const &t) {
        // ensure there's room in buffer:
        if (in_use == capacity) 
            pop();

        // construct copy of object in-place into buffer
        new(&data[write_pos++]) T(t);
        // keep pointer in bounds.
        write_pos %= capacity;
        ++in_use;
    }

    // return oldest object in queue:
    T front() {
        return data[read_pos];
    }

    // remove oldest object from queue:
    void pop() { 
        // destroy the object:
        data[read_pos++].~T();

        // keep pointer in bounds.
        read_pos %= capacity;
        --in_use;
    }

    // release the buffer:
~circular_buffer() { operator delete(data); }
};

#endif

Contrairement aux conteneurs standard, il utilise operator new et operator delete directement. De l'utilisation réelle, vous probablement ne voulez utiliser un allocateur de classe, mais pour le moment, ce serait faire plus pour distraire que de contribuer (OMI, de toute façon).

10voto

dasblinkenlight Points 264350
  1. Lorsque vous créez un objet avec new, vous êtes responsable de l'appel delete. Lorsque vous créez un objet avec make_shared, résultant shared_ptr est responsable pour le maintien de compte et en appelant delete lors de l'utilisation compte passe à zéro.
  2. Aller hors de portée ne signifie en laissant un bloc. C'est quand le destructeur est appelé, en supposant que l'objet était pas attribué new (c'est à dire c'est une pile de objet).
  3. Le seul moment où vous devez appeler un destructeur explicitement, c'est quand vous allouer l'objet d'un placement new.

3voto

Stuart Golodetz Points 12679

Pour donner une réponse détaillée à la question 3: oui, dans de rares cas, vous pouvez appeler le destructeur de manière explicite, en particulier en tant que contrepartie d’un nouveau placement, comme le fait observer dasblinkenlight.

Pour donner un exemple concret:

 #include <iostream>
#include <new>

struct Foo
{
    Foo(int i_) : i(i_) {}
    int i;
};

int main()
{
    // Allocate a chunk of memory large enough to hold 5 Foo objects.
    int n = 5;
    char *chunk = static_cast<char*>(::operator new(sizeof(Foo) * n));

    // Use placement new to construct Foo instances at the right places in the chunk.
    for(int i=0; i<n; ++i)
    {
        new (chunk + i*sizeof(Foo)) Foo(i);
    }

    // Output the contents of each Foo instance and use an explicit destructor call to destroy it.
    for(int i=0; i<n; ++i)
    {
        Foo *foo = reinterpret_cast<Foo*>(chunk + i*sizeof(Foo));
        std::cout << foo->i << '\n';
        foo->~Foo();
    }

    // Deallocate the original chunk of memory.
    ::operator delete(chunk);

    return 0;
}
 

Le but de ce genre de choses est de découpler l'allocation de mémoire de la construction d'objet.

3voto

chrisaycock Points 12900
  1. Les pointeurs -- Régulière pointeurs ne prennent pas en charge RAII. Sans explicitement delete, il y aura des ordures. Heureusement C++ a auto pointeurs que gérer cela pour vous!

  2. Champ d'application -- Penser à quand une variable devient invisible à votre programme. Habituellement, c'est à la fin de l' {block}, comme vous le soulignez.

  3. Manuel de destruction -- ne Jamais tenter cette. Laissez simplement la portée et RAII faire de la magie pour vous.

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