web-dev-qa-db-fra.com

Revive object from destructor in C ++?

Disclaimer: Je sais que c'est une mauvaise conception, je pose simplement la question par curiosité afin d'essayer d'obtenir une connaissance plus approfondie du fonctionnement du destructeur en C++.

En C #, on peut écrire: GC.KeepAlive(this) dans le destructeur d'une classe (voir l'édition ci-dessous) , et cela signifierait que le L'objet sera toujours vivant dans la mémoire même après la fin de l'appel du destructeur.

La conception de C++ permet-elle de faire revivre un objet à partir du destructeur similaire à l'algorithme C # décrit ci-dessus?

Edit: Comme indiqué par une réponse ci-dessous, GC.ReRegisterForFinalize() est plus étroitement lié à la question que GC.KeepAlive(this).

49
MathuSum Mut

La réponse courte est non. C++ n'utilise pas de récupération de place, comme Java ou C #. Quand un objet est détruit, il est détruit immédiatement. Disparu pour de bon. Rejoint le choeur invisible. Pining pour les fjords, etc ...

Et de le répéter plusieurs fois avec des mots différents pour qu'il n'y ait pas de réinterprétation possible ...

Le destructeur est appelé dans le cadre de la destruction d'objets. La destruction d'un objet consiste à invoquer le destructeur et à désallouer la mémoire utilisée pour l'objet lui-même. C'est un processus unique, pas deux processus distincts. Pendant que le destructeur est en cours d'exécution, l'objet existe toujours, à utiliser par le destructeur, mais il existe en temps emprunté. Il est acquis d'avance que l'objet va être vaporisé dès le retour du destructeur. Une fois qu'un destructeur est invoqué, l'objet va être détruit et rien ne va changer son destin.

Comprenez ceci: la raison pour laquelle un destructeur est invoqué est parce que: l'objet a été initialement alloué sur le tas avec "nouveau", et il est maintenant en cours de "suppression" d. "supprimer" signifie "supprimer", pas "supprimer peut-être". Donc, l'objet est supprimé. Ou, si l'objet a été alloué sur la pile, le thread d'exécution a quitté la portée, donc tous les objets déclarés dans la portée sont détruits. Techniquement, le destructeur est invoqué suite à la destruction de l'objet. Ainsi, l'objet est détruit. La fin.

Cela dit, C++ vous permet d'implémenter un allocateur personnalisé pour vos classes. Si vous en avez envie, vous pouvez écrire vos propres fonctions d'allocation de mémoire et de désallocation personnalisées qui implémentent la fonctionnalité que vous souhaitez. Bien que ceux-ci ne soient jamais utilisés pour les objets alloués par pile (c'est-à-dire les variables locales).

100
Sam Varshavchik

En fait, vous déformez ce que fait GC.KeepAlive Dans .NET. Il ne doit pas être utilisé dans un destructeur d'un objet pour empêcher cet objet d'être détruit - en fait, GC.KeepAlive() est vide et n'a aucune implémentation. Voir le code source .NET ici .

Il s'assure que l'objet passé en paramètre n'est pas récupéré avant l'appel à GC.KeepAlive Se produit. L'objet passé à KeepAlive en tant que paramètre peut être récupéré immédiatement après l'appel à GC.KeepAlive. Étant donné que KeepAlive n'a pas d'implémentation réelle, cela se produit uniquement sur la base du fait que le compilateur doit conserver une référence à l'objet à passer en paramètre à KeepAlive. Toute autre fonction (qui n'est pas intégrée par le compilateur ou le runtime) prenant l'objet comme paramètre pourrait également être utilisée à la place.

52
NineBerry

Voici une idée:

C* gPhoenix= nullptr;

C::~C ()
{
gPhoenix= new C (*this);  // note: loses any further-derived class ("slice")
}

Maintenant, si les objets impliqués (bases ou membres) ont vraiment des destructeurs qui font quelque chose, cela pose un problème si vous delete gPhoenix; vous aurez donc besoin de mécanismes plus élaborés en fonction de ce qu'il essaie vraiment d'accomplir. Mais vous n'avez pas d'objectifs réels, juste une exploration curieuse, alors le souligner devrait suffire.

Lorsque le corps du destructeur est appelé, l'objet est toujours parfaitement bon. Cela semble parfaitement vital et normal lorsque vous effectuez des appels de fonction membre normaux depuis le destructeur.

La mémoire propriétaire de l'objet sera récupérée, vous ne pourrez donc pas rester sur place. Et après avoir quitté le corps, d'autres destructions ont lieu automatiquement et ne peuvent pas être perturbées. Mais, vous pouvez dupliquer l'objet avant que cela ne se produise.

8
JDługosz

Comme déjà signalé , GC.KeepAlive ne fait pas ça.

Tant que .NET disparaît, il est possible de ressusciter à partir du finaliseur en utilisant GC.ReRegisterForFinalize , vous pouvez toujours obtenir une référence si vous avez un WeakReference ou GCHandle suivi ressurection, ou tout simplement donner this à quelque chose en dehors de la classe. Faire cela annulera la destruction.

C'est une vieille astuce pour détecter le garbage collection dans .NET 2.0 n'est plus pertinent , mais fonctionne toujours (en quelque sorte, le garbage collection peut maintenant être partiel et effectué en parallèle avec d'autres threads).

L'accent doit être mis sur le fait que sur .NET, vous utilisez un finaliseur , qui s'exécute avant la destruction et peut l'empêcher. Ainsi, bien qu'il soit techniquement correct que vous ne puissiez pas récupérer un objet après sa destruction - dans n'importe quelle langue - vous pouvez vous rapprocher du comportement que vous décrivez dans .NET, sauf en utilisant GC.ReRegisterForFinalize au lieu.


Sur C++, vous avez déjà reçu le bonne réponse .

6
Theraot

Ce n'est possible dans aucune langue.

Votre compréhension est un peu décalée. GC.KeepAlive marquera l'objet comme non récupérable par le garbage collector. Cela empêchera la stratégie de récupération de place de détruire l'objet et c'est utile si l'objet est utilisé dans du code non managé où le garbage collector ne peut pas garder une trace de l'utilisation. Cela ne signifie pas que l'objet est en mémoire après sa destruction.

Une fois qu'un objet commence la destruction, le code libérera des ressources (mémoire, gestionnaires de fichiers, connexions réseau). L'ordre va généralement de la classe dérivée la plus profonde à la classe de base. Si quelque chose au milieu devait empêcher la destruction, il n'y a aucune garantie que ces ressources pourraient être récupérées et que l'objet serait dans un état incohérent.

Ce que vous voulez le plus, c'est d'avoir std::shared_ptr qui garde une trace des copies et des références et ne détruit l'objet qu'une fois que personne n'en a plus besoin.

4
Sorin

Au cas où cela aiderait, la fonction destructrice et l'allocation de mémoire sont distinctes.

Le destructeur n'est qu'une fonction. Vous pouvez l'appeler explicitement. S'il ne fait rien de destructeur, le rappeler (par exemple lorsque l'objet sort du cadre ou est supprimé) n'est pas nécessairement problématique, bien que ce soit très étrange; il y a peut-être une section traitant de cela dans la norme. Voir l'exemple ci-dessous. Par exemple, certains conteneurs STL appellent explicitement le destructeur car ils gèrent séparément la durée de vie des objets et l'allocation de mémoire.

En règle générale, le compilateur insère du code pour appeler le destructeur lorsqu'une variable automatique sort de la portée ou qu'un objet alloué en tas est détruit avec delete. Cette désallocation de mémoire ne peut pas être altérée à l'intérieur du destructeur.

Vous pouvez prendre en charge l'allocation de mémoire en fournissant des implémentations supplémentaires du nouvel opérateur, ou en utilisant celles existantes comme placement new, mais le comportement par défaut général est que le compilateur appellera votre destructeur, et c'est une chance de ranger. Le fait qu'une partie de la mémoire sera par la suite effacée est hors du contrôle du destructeur.

#include <iostream>
#include <iomanip>

namespace test
{
  class GotNormalDestructor
  {
    public:
      ~GotNormalDestructor() { std::wcout << L"~GotNormalDestructor(). this=0x" << std::hex << this << L"\n"; }
  };

  class GotVirtualDestructor
  {
    public:
      virtual ~GotVirtualDestructor() { std::wcout << L"~GotVirtualDestructor(). this=0x" << std::hex << this << L"\n"; }
  };

  template <typename T>
  static void create_destruct_delete(wchar_t const name[])
  {
    std::wcout << L"create_destruct_delete<" << name << L">()\n";
    {
      T t;
      std::wcout << L"Destructing auto " << name << L" explicitly.\n";
      t.~T();
      std::wcout << L"Finished destructing " << name << L" explicitly.\n";
      std::wcout << name << L" going out of scope.\n";
    }
    std::wcout << L"Finished " << name << L" going out of scope.\n";
    std::wcout << L"\n";
  }

  template <typename T>
  static void new_destruct_delete(wchar_t const name[])
  {
    std::wcout << L"new_destruct_delete<" << name << L">()\n";
    T *t = new T;
    std::wcout << L"Destructing new " << name << L" explicitly.\n";
    t->~T();
    std::wcout << L"Finished destructing new " << name << L" explicitly.\n";
    std::wcout << L"Deleting " << name << L".\n";
    delete t;
    std::wcout << L"Finished deleting " << name << L".\n";
    std::wcout << L"\n";
  }

  static void test_destructor()
  {
    {
      std::wcout << L"\n===auto normal destructor variable===\n";
      GotNormalDestructor got_normal;
    }

    {
      std::wcout << L"\n===auto virtual destructor variable===\n";
      GotVirtualDestructor got_virtual;
    }

    {
      std::wcout << L"\n===new variables===\n";
      new_destruct_delete<GotNormalDestructor>(L"GotNormalDestructor");
      new_destruct_delete<GotVirtualDestructor>(L"GotVirtualDestructor"); 
    }

    {
      std::wcout << L"\n===auto variables===\n";
      create_destruct_delete<GotNormalDestructor>(L"GotNormalDestructor");
      create_destruct_delete<GotVirtualDestructor>(L"GotVirtualDestructor");
    }

    std::wcout << std::endl;
  }
}

int main(int argc, char *argv[])
{
  test::test_destructor();

  return 0;
}

Exemple de sortie

===auto normal destructor variable===
~GotNormalDestructor(). this=0x0x23fe1f

===auto virtual destructor variable===
~GotVirtualDestructor(). this=0x0x23fe10

===new variables===
new_destruct_delete<GotNormalDestructor>()
Destructing new GotNormalDestructor explicitly.
~GotNormalDestructor(). this=0x0x526700
Finished destructing new GotNormalDestructor explicitly.
Deleting GotNormalDestructor.
~GotNormalDestructor(). this=0x0x526700
Finished deleting GotNormalDestructor.

new_destruct_delete<GotVirtualDestructor>()
Destructing new GotVirtualDestructor explicitly.
~GotVirtualDestructor(). this=0x0x526700
Finished destructing new GotVirtualDestructor explicitly.
Deleting GotVirtualDestructor.
~GotVirtualDestructor(). this=0x0x526700
Finished deleting GotVirtualDestructor.


===auto variables===
create_destruct_delete<GotNormalDestructor>()
Destructing auto GotNormalDestructor explicitly.
~GotNormalDestructor(). this=0x0x23fdcf
Finished destructing GotNormalDestructor explicitly.
GotNormalDestructor going out of scope.
~GotNormalDestructor(). this=0x0x23fdcf
Finished GotNormalDestructor going out of scope.

create_destruct_delete<GotVirtualDestructor>()
Destructing auto GotVirtualDestructor explicitly.
~GotVirtualDestructor(). this=0x0x23fdc0
Finished destructing GotVirtualDestructor explicitly.
GotVirtualDestructor going out of scope.
~GotVirtualDestructor(). this=0x0x23fdc0
Finished GotVirtualDestructor going out of scope.
3
WaffleSouffle