c++ - open - doxywizard




C++ destructeur avec retour (8)

[D] oes retour explicite du destructeur signifie que nous ne voulons jamais le détruire?

Non. Un retour anticipé (via return; ou 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 continuent à fonctionner, voir [except.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 appelé pour chacun des sous-objets entièrement construits de l'objet ...

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

Je veux faire en sorte qu'un certain objet ne soit détruit que par un autre destructeur d'objets, c'est-à-dire seulement quand l'autre objet est prêt à être détruit.

Il semble que la question soit ancrée dans la question de la propriété. Supprimer l'objet "possédé" seulement une fois que le parent est détruit dans un idiome très commun et réalisé avec l'un des (mais non limité à);

  • Composition, c'est une variable membre automatique (ie "stack based")
  • Un std::unique_ptr<> pour exprimer la propriété exclusive de l'objet dynamique
  • Un std::shared_ptr<> pour exprimer la propriété partagée d'un objet dynamique

Étant donné 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 la vérification de condition if dans l'exemple de code. Il laisse entendre que l'état est lié à la propriété et à la durée de vie. Ils ne sont pas tous 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 du code de nettoyage), mais j'éviterais le lien direct avec la propriété de l'objet. Cela peut être une meilleure idée de reconsidérer certaines des sémantiques impliquées ici, ou permettre à la construction et à la destruction «naturelles» de dicter les états de début et de fin de l'objet.

Note de côté ; Si vous devez vérifier un état dans le destructeur (ou affirmer une condition de fin), une alternative au throw consiste à appeler std::terminate (avec un enregistrement) si cette condition n'est pas remplie. Cette approche est similaire au comportement standard et au résultat lorsqu'une exception est levée lors du déroulement de la pile suite à une exception déjà levée. C'est aussi le comportement standard quand un std::thread sort avec une exception non gérée.

[D] oes retour explicite du destructeur signifie que nous ne voulons jamais le détruire?

Non (voir ci-dessus). Le code suivant illustre ce comportement; lié ici et une version dynamique . Le noexcept(false) est nécessaire pour éviter l'appel de std::terminate() .

#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

En C ++ si on définit un destructeur de classe comme:

~Foo(){
   return;
}

En appelant ce destructeur, l'objet de Foo sera détruit ou retourne explicitement du destructeur, ce qui signifie que nous ne voulons jamais le détruire.

Je veux faire en sorte qu'un certain objet ne soit détruit que par un autre destructeur d'objets, c'est-à-dire seulement quand 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 cherché en ligne et je n'arrivais pas à trouver une réponse à ma question. J'ai également essayé de le découvrir moi-même en passant du code étape par étape avec un débogueur, mais je n'arrive pas à obtenir un résultat concluant.


renvoie explicitement du destructeur signifie que nous ne voulons jamais le détruire.

Non.

Le destructeur est une fonction de sorte que vous pouvez utiliser le mot-clé return intérieur de celui-ci mais cela n'empêchera pas la destruction de l'objet, une fois que vous êtes dans le destructeur, vous détruisez déjà votre objet. se produire avant.

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


Dans ce cas, vous pouvez utiliser une surcharge propre à la classe de l'opérateur de suppression. Donc pour vous Class2 vous pourriez quelque chose comme ça

class Class2
{
    static void operator delete(void* ptr, std::size_t sz)
    {
        std::cout << "custom delete for size " << sz << '\n';
        if(finished)
            ::operator delete(ptr);
    }

    bool Finished;
}

Alors si vous définissez le fini à vrai avant la suppression, la suppression réelle sera appelée. Notez que je ne l'ai pas testé, je viens de modifier le code que j'ai trouvé ici http://en.cppreference.com/w/cpp/memory/new/operator_delete

Class1::~Class1()
{
    class2->Finished = true;
    delete class2;
}

Donc, comme tous les autres l'ont souligné, le return n'est pas une solution.

La première chose que j'ajouterais, c'est que vous ne devriez pas vous inquiéter à ce sujet. Sauf si votre professeur a explicitement demandé. Ce serait très étrange si vous ne pouviez pas faire confiance à la classe externe pour seulement supprimer votre classe au bon moment, et je me dis que personne d'autre ne le voit. Si le pointeur est passé, le pointeur serait très probablement shared_ptr / weak_ptr , et le laisserait détruire votre classe au bon moment.

Mais, hé, il est bon de se demander comment nous pourrions résoudre un problème étrange si jamais cela se produisait, si nous apprenions quelque chose (et ne perdons pas de temps en respectant une date butoir!)

Alors quoi pour une solution? Si vous pouvez au moins faire confiance au destructeur de Class1 pour ne pas détruire votre objet trop tôt, vous pouvez simplement déclarer le destructeur de Class2 comme privé, puis déclarer le destructeur de Class1 comme ami de Class2, comme ceci:

class Class2;

class Class1 {

    Class2* myClass2;

public:
    ~Class1();
};

class Class2 {
private:
        ~Class2();

friend Class1::~Class1();
};

Class1::~Class1() {
    delete myClass2;
}

Class2::~Class2() {
}

En prime, vous n'avez pas besoin du drapeau 'status'; ce qui est bon -si quelqu'un voulait que ce soit mauvais pour visser avec vous, pourquoi ne pas mettre le drapeau d'état à FINISHED nulle part ailleurs, et ensuite appeler delete ?

De cette façon, vous avez la garantie que l'objet ne peut être détruit nulle part ailleurs que dans le destructeur de Class1.

Bien sûr, le destructeur de Class1 a accès à tous les membres privés de Class2. Peu importe, après tout, Class2 est sur le point d'être détruit! Mais si c'est le cas, nous pouvons imaginer des façons encore plus alambiquées de contourner ce problème; pourquoi pas. Par exemple:

class Class2;

class Class1 {
private:
    int status;

    Class2* myClass2;

public:
    ~Class1();
};

class Class2Hidden {
private:
      //Class2 private members
protected:
    ~Class2Hidden();
public:
    //Class2 public members
};

class Class2 : public Class2Hidden {
protected:
        ~Class2();

friend Class1::~Class1();
};

Class1::~Class1() {
    delete myClass2;
}

Class2Hidden::~Class2Hidden() {
}

Class2::~Class2() {
}

De cette façon, les membres publics seront toujours disponibles dans la classe dérivée, mais les membres privés seront réellement privés. ~ Class1 n'aura accès qu'aux membres privés et protégés de Class2 et aux membres protégés de Class2Hidden; qui dans ce cas est seulement les destructeurs. Si vous devez protéger un membre protégé de Class2 contre le destructeur de Class1 ... il existe des moyens, mais cela dépend vraiment de ce que vous faites.

Bonne chance!


Non. return signifie simplement quitter la méthode, elle n'arrête pas la destruction de l'objet.

Aussi, pourquoi voudriez-vous? Si l'objet est alloué sur la pile et que vous avez réussi à arrêter la destruction, l'objet vivra sur une partie récupérée de la pile qui sera probablement écrasée par l'appel de la fonction suivante, qui écrira toute la mémoire de vos objets et créera un comportement indéfini .

De même, si l'objet est alloué sur le tas et que vous avez réussi à éviter la destruction, vous aurez une fuite de mémoire car le code appelant delete supposera qu'il n'a pas besoin de garder un pointeur sur l'objet alors qu'il est toujours là et en prenant la mémoire que personne ne se réfère.


Selon la norme C ++ (12.4 Destructeurs)

8 Après l'exécution du corps du destructeur et la destruction des objets automatiques alloués dans le corps, un destructeur pour la classe X appelle les destructeurs pour les membres de données non statiques directes non-variables de X, les destructeurs pour les classes directes de X et si X est le type de la classe la plus dérivée (12.6.2), son destructeur appelle les destructeurs pour les 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, ignorant les éventuels destructeurs de déroutement virtuels dans des classes plus dérivées. Les bases et les membres sont détruits dans l'ordre inverse de l'achèvement de leur constructeur (voir 12.6.2). Une déclaration de retour (6.6.3) dans un destructeur peut ne pas retourner directement à l'appelant; avant de transférer le contrôle à l'appelant, les destructeurs pour les membres et les bases sont appelés. Les destructeurs d'éléments d'un tableau sont appelés dans l'ordre inverse de leur construction (voir 12.6).

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


Vous pouvez créer une nouvelle méthode pour que l'objet se suicider et garder le destructeur vide, alors quelque chose comme ça fera le travail que vous aimeriez faire:

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

void Class2::DeleteMe()
{
   if (status == FINISHED)
   {
      delete this;
   }
 }

 Class2::~Class2()
 {
 }

~Foo(){
   return;
}

signifie exactement la même chose que:

~Foo() {}

C'est semblable à une fonction void ; atteindre la fin sans return; déclaration est la même que d'avoir le return; à la fin.

Le destructeur contient des actions qui sont effectuées lorsque le processus de destruction d'un Foo a déjà commencé. Il n'est pas possible d'abandonner un processus de destruction sans annuler le programme entier.







destructor