Ordre de destruction C ++: appel d'un destructeur de champ avant le destructeur de classe
Existe-t-il un moyen d'appeler un destructeur de champ avant le destructeur de classe?
Supposons que j'ai 2 classes Small
et Big
, et Big
contienne une instance de Small
comme son champ en tant que tel:
class Small
{
public:
~Small() {std::cout << "Small destructor" << std::endl;}
};
class Big
{
public:
~Big() {std::cout << "Big destructor" << std::endl;}
private:
Small small;
};
int main()
{
Big big;
}
Ceci, bien sûr, appelle le grand destructeur avant le petit destructeur:
Big destructor
Small destructor
J'ai besoin que le Small
destructeur soit appelé avant le Big
destructeur car il effectue le nettoyage nécessaire pour le Big
destructeur.
Je pouvais:
- appelez
small.~Small()
explicitement le destructeur. -> Ceci, cependant, appelle leSmall
destructeur deux fois: une fois explicitement, et une fois après l'Big
exécution du destructeur. - avoir un
Small*
comme champ et appelerdelete small;
leBig
destructeur
Je suis conscient que je peux avoir une fonction dans la Small
classe qui fait le nettoyage et l'appeler dans le Big
destructeur, mais je me demandais s'il y avait un moyen d'inverser l'ordre du destructeur.
Y a-t-il une meilleure façon de faire cela?
appelez explicitement le destructeur small. ~ Small (). -> Ceci, cependant, appelle le petit destructeur deux fois: une fois explicitement, et une fois après l'exécution du gros destructeur.
Eh bien, je ne sais pas pourquoi vous voulez continuer avec cette conception défectueuse, mais vous pouvez résoudre le problème décrit dans votre première puce en utilisant le placement nouveau.
Il suit un exemple de travail minimal:
#include <iostream>
struct Small {
~Small() {std::cout << "Small destructor" << std::endl;}
};
struct Big {
Big() { ::new (storage) Small; }
~Big() {
reinterpret_cast<Small *>(storage)->~Small();
std::cout << "Big destructor" << std::endl;
}
Small & small() {
return *reinterpret_cast<Small *>(storage);
}
private:
unsigned char storage[sizeof(Small)];
};
int main() {
Big big;
}
Vous n'avez plus de variable de type Small
, mais avec quelque chose comme la small
fonction membre dans l'exemple, vous pouvez facilement la contourner.
L'idée est que vous réservez suffisamment d'espace pour construire un sur place Small
, puis vous pouvez invoquer son destructeur explicitement comme vous l'avez fait. Il ne sera pas appelé deux fois, car tout ce que la Big
classe doit libérer est un tableau de unsigned char
s.
De plus, vous ne le stockerez pas Small
directement dans le stockage dynamique, car en fait, vous utilisez une donnée membre de votre Big
pour le créer.
Cela étant dit, je vous suggère de l'allouer sur le stockage dynamique à moins que vous n'ayez une bonne raison de faire autrement. Utilisez a std::unique_ptr
et réinitialisez-le au début du destructeur de Big
. Votre Small
va disparaître avant que le corps du destructeur ne soit réellement exécuté comme prévu et dans ce cas également, le destructeur ne sera pas appelé deux fois.
ÉDITER
Comme suggéré dans les commentaires, std::optional
peut être une autre solution viable au lieu de std::unique_ptr
. Gardez à l'esprit que cela std::optional
fait partie du C ++ 17, donc si vous pouvez l'utiliser, cela dépend principalement de la révision de la norme à laquelle vous devez adhérer.