web-dev-qa-db-fra.com

Est-ce que supprimer est autorisé?

Est-il permis de delete this; si l'instruction de suppression est la dernière instruction qui sera exécutée sur cette instance de la classe? Bien sûr, je suis sûr que l'objet représenté par le pointeur this- est newly créé.

Je pense à quelque chose comme ça:

void SomeModule::doStuff()
{
    // in the controller, "this" object of SomeModule is the "current module"
    // now, if I want to switch over to a new Module, eg:

    controller->setWorkingModule(new OtherModule());

    // since the new "OtherModule" object will take the lead, 
    // I want to get rid of this "SomeModule" object:

    delete this;
}

Puis-je faire ceci?

217
Martijn Courteaux

C++ FAQ Lite a une entrée spécialement pour cela

Je pense que cette citation résume bien

Tant que vous faites attention, un objet peut se suicider (supprimez-le).

225
JaredPar

Oui, delete this; a défini des résultats, tant que (comme vous l'avez noté) vous assurez que l'objet a été alloué de manière dynamique et (bien sûr) n'essayez jamais de l'utiliser après sa destruction. Au fil des ans, de nombreuses questions ont été posées sur ce que la norme dit spécifiquement à propos de delete this;, par opposition à la suppression d'un autre pointeur. La réponse à cette question est assez courte et simple: cela ne dit pas grand chose. Il indique simplement que l'opérande de delete doit être une expression désignant un pointeur sur un objet ou un tableau d'objets. Il entre dans un certain nombre de détails sur des choses telles que la façon dont il détermine quelle fonction (le cas échéant) de désallocation appeler pour libérer la mémoire, mais la section entière sur delete (§ [expr.delete]) ne le fait pas. mentionner delete this; spécifiquement. La section sur les destructeurs mentionne delete this en un seul endroit (§ [class.dtor]/13):

Au moment de la définition d'un destructeur virtuel (y compris une définition implicite (15.8)), la fonction de désallocation de tableau est déterminée comme si, pour l'expression, cette suppression apparaissait dans un destructeur non virtuel de la classe du destructeur (voir 8.3.5). ).

Cela tend à soutenir l'idée que la norme considère que delete this; est valide. Si elle était invalide, son type n'aurait pas de sens. Pour autant que je sache, c’est le seul endroit où la norme mentionne delete this;.

Quoi qu'il en soit, certains considèrent delete this comme un mauvais bidouillage et disent à tous ceux qui écouteront que cela doit être évité. Un problème fréquemment cité est la difficulté de s'assurer que les objets de la classe ne sont jamais alloués de manière dynamique. D'autres le considèrent comme un langage parfaitement raisonnable et l'utilisent tout le temps. Personnellement, je suis quelque part au milieu: je l'utilise rarement, mais n'hésitez pas à le faire quand cela semble être le bon outil pour le poste.

La première fois que vous utilisez cette technique est avec un objet qui a une vie qui lui est presque entièrement propre. Un exemple cité par James Kanze est un système de facturation/suivi sur lequel il travaillait pour une compagnie de téléphone. Lorsque vous démarrez un appel téléphonique, quelque chose en prend note et crée un objet phone_call. À partir de ce moment, l’objet phone_call gère les détails de l’appel téléphonique (établit une connexion lorsque vous composez un numéro, ajoutez une entrée à la base de données pour indiquer le début de l’appel, connectez éventuellement plus de personnes si vous effectuez une conférence téléphonique. , etc.) Lorsque les dernières personnes en communication raccrochent, l’objet phone_call effectue sa comptabilité finale (par exemple, ajoute une entrée à la base de données pour indiquer à quel moment vous avez raccroché, afin de pouvoir calculer la durée de votre travail. appel était) et puis se détruit. La durée de vie de l'objet phone_call est basée sur le moment où la première personne commence l'appel et les derniers appels qui la quittent - du point de vue du reste du système, c'est fondamentalement totalement arbitraire, donc vous ne peut pas le lier à une portée lexicale dans le code, ou à quoi que ce soit de cet ordre.

Pour tous ceux qui se soucient de la fiabilité de ce type de codage: si vous passez un appel téléphonique depuis, vers ou depuis presque toute l'Europe, il y a de bonnes chances qu'il soit géré (au moins en partie) par code cela fait exactement cela.

80
Jerry Coffin

Si cela vous fait peur, il y a un hack parfaitement légal:

void myclass::delete_me()
{
    std::unique_ptr<myclass> bye_bye(this);
}

Je pense que delete this est cependant idiomatique en C++, et je ne présente cela que comme une curiosité.

Dans certains cas, cette construction est réellement utile - vous pouvez supprimer l'objet après avoir levé une exception nécessitant des données de membre à partir de l'objet. L'objet reste valide jusqu'à ce que le lancer ait lieu.

void myclass::throw_error()
{
    std::unique_ptr<myclass> bye_bye(this);
    throw std::runtime_exception(this->error_msg);
}

Remarque: si vous utilisez un compilateur plus ancien que C++ 11, vous pouvez utiliser std::auto_ptr à la place de std::unique_ptr, il fera la même chose.

45
Mark Ransom

C++ a été conçu pour faciliter la réutilisation du code. En général, C++ doit être écrit de sorte qu'il fonctionne, que la classe soit instanciée sur le tas, dans un tableau ou sur la pile. "Supprimer ceci" est une très mauvaise pratique de codage car il ne fonctionnera que si une seule instance est définie sur le tas; et il vaut mieux ne pas utiliser une autre instruction delete, qui est généralement utilisée par la plupart des développeurs pour nettoyer le tas. Cela suppose également qu'aucun programmeur de maintenance à l'avenir ne résoudra une fuite de mémoire mal perçue en ajoutant une instruction delete.

Même si vous savez à l'avance que votre plan actuel consiste à allouer une seule instance sur le tas, que se passe-t-il si un développeur optimiste se présente à l'avenir et décide de créer une instance sur la pile? Ou bien s'il coupe et colle certaines parties de la classe dans une nouvelle classe qu'il a l'intention d'utiliser sur la pile? Lorsque le code atteint "delete this", il disparaîtra et le supprimera, mais lorsque l'objet sera hors de portée, il appellera le destructeur. Le destructeur essaiera ensuite de le supprimer à nouveau et vous serez arrosé. Dans le passé, faire quelque chose comme ça aurait gâché non seulement le programme, mais également le système d’exploitation et l’ordinateur, et il faudrait redémarrer. Dans tous les cas, cela n'est PAS vivement recommandé et devrait presque toujours être évité. Je devrais être désespéré, sérieusement plâtré ou détester vraiment la société pour laquelle je travaillais pour écrire du code qui l'a fait.

22
Bob Bryan

C'est autorisé (n'utilisez tout simplement pas l'objet après cela), mais je n'écrirais pas un tel code à la pratique. Je pense que delete this devrait apparaître uniquement dans les fonctions appelées release ou Release et ressemblant à: void release() { ref--; if (ref<1) delete this; }.

19

Dans le modèle COM (Component Object Model), la construction delete this peut faire partie de la méthode Release appelée à chaque fois que vous souhaitez publier un objet recherché:

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}
13
UnknownGosu

C'est l'idiome de base pour les objets à comptage de références.

Le comptage de références est une forme solide de récupération de place déterministe. Il garantit que les objets gèrent leur durée de vie OWN au lieu de compter sur des pointeurs "intelligents", etc. pour le faire à leur place. L'accès à l'objet sous-jacent s'effectue uniquement via des pointeurs intelligents "Référence", conçus pour que les pointeurs incrémentent et décrémentent un entier membre (le nombre de références) dans l'objet réel.

Lorsque la dernière référence est retirée de la pile ou est supprimée, le compte de références passe à zéro. Le comportement par défaut de votre objet sera alors un appel à "delete this" pour ramasser les ordures. Les bibliothèques que j'écris fournissent un appel virtuel protégé "CountIsZero" dans la classe de base afin que vous puissiez remplacer ce comportement pour des opérations telles que la mise en cache.

La clé de cette sécurisation n’autorise pas les utilisateurs à accéder au CONSTRUCTEUR de l’objet en question (qu’il soit protégé), mais leur permet plutôt d’appeler un membre statique, le "static Reference CreateT (...)". De cette façon, vous SAVEZ avec certitude qu'ils sont toujours construits avec du "nouveau" ordinaire et qu'aucun pointeur brut n'est disponible, aussi "supprimer ceci" ne va jamais exploser.

8
Zack Yezek

Vous pouvez le faire Cependant, vous ne pouvez pas affecter à cela. Par conséquent, la raison pour laquelle vous indiquez "Je veux changer d’avis" semble très discutable. La meilleure méthode, à mon avis, serait que l’objet qui détient la vue remplace cette vue.

Bien sûr, vous utilisez des objets RAII et vous n'avez donc pas besoin d'appeler supprimer du tout ... n'est-ce pas?

7
Edward Strange

C'est une vieille question à laquelle on a répondu, mais @Alexandre a demandé "Pourquoi quelqu'un voudrait-il faire cela?", Et j'ai pensé que je pourrais fournir un exemple d'utilisation que je considère cet après-midi.

Code hérité. Utilise les pointeurs nus Obj * obj avec un suppr obj à la fin.

Malheureusement, j'ai parfois besoin, pas souvent, de garder l'objet en vie plus longtemps.

Je considère en faire un pointeur intelligent compté de référence. Mais il y aurait beaucoup de code à changer, si je devais utiliser ref_cnt_ptr<Obj> partout. Et si vous mélangez nue Obj * et ref_cnt_ptr, vous pouvez obtenir implicitement la suppression de l'objet lorsque le dernier ref_cnt_ptr disparaît, même s'il y a des Obj * encore en vie.

Donc, je pense à créer un explicit_delete_ref_cnt_ptr. C'est à dire. un pointeur compté de références où la suppression est effectuée uniquement dans une routine de suppression explicite. En l'utilisant dans le seul endroit où le code existant connaît la durée de vie de l'objet, ainsi que dans mon nouveau code qui maintient l'objet en vie plus longtemps.

Incrémenter et décrémenter le nombre de références lorsque explicit_delete_ref_cnt_ptr est manipulé.

Mais PAS libérant lorsque le nombre de références est considéré comme égal à zéro dans le destructeur explicit_delete_ref_cnt_ptr.

Libérer uniquement lorsque le nombre de références est considéré comme égal à zéro dans une opération de type suppression explicite. Par exemple. dans quelque chose comme:

template<typename T> class explicit_delete_ref_cnt_ptr { 
 private: 
   T* ptr;
   int rc;
   ...
 public: 
   void delete_if_rc0() {
      if( this->ptr ) {
        this->rc--;
        if( this->rc == 0 ) {
           delete this->ptr;
        }
        this->ptr = 0;
      }
    }
 };

OK, quelque chose comme ça. Il est un peu inhabituel qu'un type de pointeur compté par référence ne supprime pas automatiquement l'objet pointé dans le destructeur ptr renvoyé. Mais il semble que cela rende le mélange de pointeurs nus et pointés un peu plus sûr.

Mais jusqu'à présent, pas besoin de supprimer ceci.

Mais alors, je me suis dit: si l’objet pointé, la pointee, sait qu’il est compté, par exemple. Si le nombre est à l'intérieur de l'objet (ou dans une autre table), alors la routine delete_if_rc0 peut être une méthode de l'objet pointee, pas le pointeur (intelligent).

class Pointee { 
 private: 
   int rc;
   ...
 public: 
   void delete_if_rc0() {
        this->rc--;
        if( this->rc == 0 ) {
           delete this;
        }
      }
    }
 };

En fait, il n'est pas nécessaire que ce soit une méthode membre, mais cela pourrait être une fonction libre:

map<void*,int> keepalive_map;
template<typename T>
void delete_if_rc0(T*ptr) {
        void* tptr = (void*)ptr;
        if( keepalive_map[tptr] == 1 ) {
           delete ptr;
        }
};

(Au fait, je sais que le code n’est pas tout à fait correct. Il devient moins lisible si j’ajoute tous les détails, alors je le laisse comme ça.)

4
Krazy Glew

Supprimer ceci est légal tant que l'objet est dans le tas. Vous auriez besoin d'exiger que l'objet soit uniquement tas. La seule façon de le faire est de protéger le destructeur. De cette façon, delete peut être appelé UNIQUEMENT à partir de la classe. Vous aurez donc besoin d'une méthode qui en assurerait la suppression.

0