39 votes

Destructeur C++ avec retour

En C++, si nous définissons un destructeur de classe comme :

~Foo(){
   return;
}

Lors de l'appel de ce destructeur, l'objet de Foo être détruit ou est-ce que le retour explicite du destructeur signifie-t-il que nous ne voulons jamais le détruire ?

Je veux faire en sorte qu'un certain objet soit détruit uniquement par le destructeur d'un autre objet, c'est-à-dire uniquement lorsque l'autre objet est prêt à être détruit.

Exemple :

class Class1{
...
Class2* myClass2;
...
};

Class1::~Class1(){
    myClass2->status = FINISHED;
    delete myClass2;
}

Class2::~Class2(){
    if (status != FINISHED) return;
}

J'ai fait des recherches en ligne et je n'ai pas trouvé de réponse à ma question. J'ai également essayé de la résoudre moi-même en examinant le code étape par étape avec un débogueur, mais je n'ai pas obtenu de résultat concluant.

21 votes

Vous ne pouvez pas ne pas détruire un objet une fois qu'un destructeur a été entré (voir mon deuxième commentaire pour plus de clarté). Qu'est-ce que cela signifierait de ne pas détruire une variable de pile ?

4 votes

Imaginez { Foo x; } Que signifie " ne pas détruire " l'objet ?

0 votes

isocpp.org/wiki/faq/exceptions#dtors-shouldnt-throw -- alors peut-être que vous pouvez lancer une exception pendant la destruction d'une variable de tas... mais ce n'est pas quelque chose autour duquel concevoir un logiciel.

46voto

songyuanyao Points 2265

Non, vous ne pouvez pas empêcher l'objet d'être détruit par l'instruction return, cela signifie simplement que l'exécution du corps du dtor se terminera à ce moment-là. Après cela, il sera toujours détruit (y compris ses membres et ses bases), et la mémoire sera toujours désallouée.

Il se peut que vous lanciez une exception.

Class2::~Class2() noexcept(false) {
    if (status != FINISHED) throw some_exception();
}

Class1::~Class1() {
    myClass2->status = FINISHED;
    try {
        delete myClass2;
    } catch (some_exception& e) {
        // what should we do now?
    }
}

Notez que c'est un terrible idée en effet. Vous feriez mieux de reconsidérer la conception, je suis sûr qu'il doit y en avoir une meilleure. Le fait de lancer une exception n'empêchera pas la destruction de ses bases et de ses membres, mais permettra simplement d'obtenir le résultat du processus de Class2 Le Dtor. Et ce qui pourrait en être fait n'est toujours pas clair.

55 votes

"pas une bonne idée" ne semble pas une formulation assez forte - c'est une idée terrible, terrible, terrible !

14 votes

Ce code est toujours une idée terrible et on sait tous les deux que l'OP va l'utiliser de toute façon.

0 votes

Vous pourriez peut-être ajouter que la bonne façon d'empêcher la destruction est de rendre le destructeur privé ?

24voto

Matt McNabb Points 14273
~Foo(){
   return;
}

signifie exactement la même chose que :

~Foo() {}

Il est similaire à un void fonction ; atteindre la fin sans un return; est la même chose que d'avoir return; à la fin.

Le destructeur contient des actions qui sont exécutées lorsque le processus de destruction d'un objet de type Foo a déjà commencé. Il n'est pas possible d'interrompre un processus de destruction sans interrompre l'ensemble du programme.

0 votes

J'ose dire que l'abandon de l'ensemble du programme est proche de la destruction de cette instance de toute façon ;)

17voto

Niall Points 6133

[Le retour explicite du destructeur signifie-t-il que nous ne voulons plus jamais le détruire ?

Non. Un retour anticipé (via return; o throw ... ) signifie seulement que le reste du corps du destructeur n'est pas exécuté. La base et les membres sont toujours détruits et leurs destructeurs sont toujours exécutés, voir [sauf.ctor]/3 .

Pour un objet de type classe de toute durée de stockage dont l'initialisation ou la destruction est terminée par une exception, le destructeur est invoqué pour chacun des sous-objets entièrement construits de l'objet...

Voir ci-dessous des exemples de code illustrant ce comportement.


Je veux faire en sorte qu'un certain objet soit détruit uniquement par le destructeur d'un autre objet, c'est-à-dire uniquement lorsque l'autre objet est prêt à être détruit.

Il semble que la question soit ancrée dans le problème de la propriété. La suppression de l'objet "propriétaire" seulement une fois que le parent est détruit est un idiome très commun et est réalisé avec l'une des méthodes suivantes (mais pas seulement) ;

  • Composition, il s'agit d'une variable membre automatique (c'est-à-dire "basée sur la pile").
  • A std::unique_ptr<> pour exprimer la propriété exclusive de l'objet dynamique
  • A std::shared_ptr<> pour exprimer la propriété partagée d'un objet dynamique

Compte tenu de l'exemple de code dans l'OP, le std::unique_ptr<> peut être une alternative appropriée ;

class Class1 {
  // ...
  std::unique_ptr<Class2> myClass2;
  // ...
};

Class1::~Class1() {
    myClass2->status = FINISHED;
    // do not delete, the deleter will run automatically
    // delete myClass2;
}

Class2::~Class2() {
    //if (status != FINISHED)
    //  return;
    // We are finished, we are being deleted.
}

Je note que if vérification de la condition dans le code d'exemple. Il laisse entendre que l'état est lié à la propriété et à la durée de vie. Ce n'est pas la même chose ; bien sûr, vous pouvez lier l'objet atteignant un certain état à sa durée de vie "logique" (c'est-à-dire exécuter un code de nettoyage), mais j'éviterais le lien direct avec la propriété de l'objet. Ce serait peut-être une meilleure idée de reconsidérer la sémantique impliquée ici, ou de permettre la construction et la destruction "naturelles" de dicter les états de début et de fin de l'objet.

Note complémentaire ; si vous devez vérifier un état dans le destructeur (ou affirmer une condition finale), une alternative à la fonction throw est d'appeler std::terminate (avec une certaine journalisation) si cette condition n'est pas remplie. Cette approche est similaire au comportement et au résultat standard lorsqu'une exception est levée lors du déroulement de la pile à la suite d'une exception déjà levée. C'est également le comportement standard lorsqu'une std::thread se termine par une exception non gérée.


[Le retour explicite du destructeur signifie-t-il que nous ne voulons plus jamais le détruire ?

Non (voir ci-dessus). Le code suivant démontre ce comportement ; lié ici et un version dynamique . El noexcept(false) est nécessaire pour éviter std::terminate() étant appelé .

#include <iostream>
using namespace std;
struct NoisyBase {
    NoisyBase() { cout << __func__ << endl; }
    ~NoisyBase() { cout << __func__ << endl; }
    NoisyBase(NoisyBase const&) { cout << __func__ << endl; }
    NoisyBase& operator=(NoisyBase const&) { cout << __func__ << endl; return *this; }    
};
struct NoisyMember {
    NoisyMember() { cout << __func__ << endl; }
    ~NoisyMember() { cout << __func__ << endl; }
    NoisyMember(NoisyMember const&) { cout << __func__ << endl; }
    NoisyMember& operator=(NoisyMember const&) { cout << __func__ << endl; return *this; }    
};
struct Thrower : NoisyBase {
    Thrower() { cout << __func__ << std::endl; }
    ~Thrower () noexcept(false) {
        cout << "before throw" << endl;
        throw 42;
        cout << "after throw" << endl;
    }
    NoisyMember m_;
};
struct Returner : NoisyBase {
    Returner() { cout << __func__ << std::endl; }
    ~Returner () noexcept(false) {
        cout << "before return" << endl;
        return;
        cout << "after return" << endl;
    }
    NoisyMember m_;
};
int main()
{
    try {
        Thrower t;
    }
    catch (int& e) {
        cout << "catch... " << e << endl;
    }
    {
        Returner r;
    }
}

A la sortie suivante ;

NoisyBase
NoisyMember
Thrower
before throw
~NoisyMember
~NoisyBase
catch... 42
NoisyBase
NoisyMember
Returner
before return
~NoisyMember
~NoisyBase

8voto

Vlad from Moscow Points 36219

Selon la norme C++ (12.4 Destructeurs)

8 Après avoir exécuté le corps du destructeur et détruit les éventuelles objets automatiques alloués à l'intérieur du corps, un destructeur de la classe X appelle les destructeurs des membres directs non statiques et non-variants de la classe X directs de X, les destructeurs des classes de base directes de X et, si X est la classe est le type de la classe la plus dérivée (12.6.2), son destructeur appelle les destructeurs des classes de base virtuelles de X. Tous les destructeurs sont appelés comme s'ils étaient référencés avec un nom qualifié, c'est-à-dire en ignorant les éventuels destructeurs virtuels de surcharge dans les classes plus dérivées. Les bases et les membres sont détruits dans l'ordre inverse de l'achèvement de la fonction de leur constructeur (voir 12.6.2). Une instruction de retour (6.6.3) dans un destructeur de type destructeur peut ne pas retourner directement à l'appelant ; avant le avant de transférer le contrôle à l'appelant, les destructeurs des membres et des bases sont appelés. et des bases sont appelés. Les destructeurs des éléments d'un tableau sont appelés dans l'ordre inverse de leur construction (voir 12.6).

Ainsi, une déclaration de retour n'empêche pas l'objet pour lequel le destructeur est appelé d'être détruit.

7voto

Drax Points 2922

le fait de retourner explicitement du destructeur signifie-t-il que nous ne voulons jamais le détruire ?

Non.

Le destructeur est une fonction, vous pouvez donc utiliser la fonction return mais cela n'empêchera pas la destruction de l'objet. Une fois que vous êtes dans le destructeur, vous êtes déjà en train de détruire votre objet, donc toute logique qui veut empêcher cela devra intervenir avant.

Pour une raison quelconque, je pense intuitivement que votre problème de conception peut être résolu avec une shared_ptr et peut-être un suppresseur personnalisé, mais cela nécessiterait plus d'informations sur ledit problème.

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