[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
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.
0 votes
Dans votre exemple, vous présumer qu'un
Class2
ne peut exister qu'en tant queClass1
membre. Cela pourrait ne pas être vrai....1 votes
Il s'agit d'un projet scolaire dans lequel nous avons un fichier d'en-tête donné contenant une classe "enveloppe" (
Class1
) par lequel la fonctionnalité de notre programme sera testée. Nous ne sommes pas autorisés à apporter des modifications à cette classe enveloppe, toute l'implémentation se fait par le biais d'une classe amie (Class2
) deClass1
.Class1
contient un pointeur versClass2
. Il n'y a rien qui empêche quelqu'un de fairedelete myClass2
ce qui casserait le programme, à moins queClass1
est prêt à être résilié. Je reconnais que la conception n'est pas idéale, mais j'essaie de trouver un moyen de rendre tout "sûr" avec ce qui est donné.14 votes
On dirait que nous avons un Le problème XY ici, et votre vrai problème est de savoir comment exprimer dans le code qu'une certaine classe dépend de de l'existence d'autrui. La dépendance peut s'exprimer de plusieurs manières (voir la réponse de Niall), mais empêcher la destruction est une mauvaise direction à prendre.
0 votes
Je me demande s'il s'agit d'une sorte de problème de "double XY", c'est-à-dire qu'il y a un autre niveau de "pourquoi" ici. Si vous ne pouvez pas changer
Class1
comment vous assurez-vous que~Class1
fixemyClass2->status = FINISHED
? SiClass1
est en fait juste une sorte de gabarit de test pourClass2
pour ne jamais être utilisé à d'autres fins, devrions-nous même essayez pour mettre des caractéristiques dansClass2
pour contourner les problèmes de sécurité perçus dansClass1
?0 votes
@xaxxon : En fait, vous pouvez, en appelant
std::terminate()
mais je reconnais que c'est un cas particulier.0 votes
Personne n'aborde le problème de fond ici : "Destructeur" en tant que nom est trompeur. Vous devriez penser à la
~Foo()
fonctionne plus comme un "souhait de mourir", un dernier souffle avant de quitter ce monde pour toujours. Vous pouvez mettre ici tout ce que vous voulez que l'objet fasse avant de mourir. C'est tout ce que c'est.