Destructeurs Singleton
Les objets Singleton qui n'utilisent pas de compteurs d'instance/de référence devraient-ils être considérés comme des fuites de mémoire en C++?
Sans compteur qui appelle à la suppression explicite de l'instance singleton lorsque le nombre est nul, comment l'objet est-il supprimé? Est-il nettoyé par le système d'exploitation lorsque l'application est terminée? Et si ce Singleton avait alloué de la mémoire sur le tas?
En un mot, dois-je appeler un destructeur de Singelton ou Puis-je compter sur son nettoyage lorsque le l'application se termine?
12 réponses
Vous pouvez compter sur son nettoyage par le système d'exploitation.
Cela dit, si vous êtes dans un langage garbage collected avec des finaliseurs plutôt que des destructeurs, vous pouvez avoir une procédure d'arrêt gracieuse qui peut arrêter proprement vos singletons directement afin qu'ils puissent libérer des ressources critiques au cas où il y aurait des ressources système qui ne seront pas correctement nettoyées en mettant simplement fin à l'application. C'est parce que les finaliseurs fonctionnent sur une sorte de "meilleur effort" dans la plupart des langues. D'autre part, il y a très très peu de ressources qui ont besoin de ce genre de fiabilité. poignées de fichiers, mémoire, etc. tous reviennent à L'OS proprement indépendamment.
Si vous utilisez un singleton qui est alloué paresseusement (c'est-à-dire avec un idiome de verrouillage triple-check) dans un langage comme c++ avec de vrais destructeurs plutôt que des finaliseurs, vous ne pouvez pas compter sur son destructeur appelé pendant l'arrêt du programme. Si vous utilisez une seule instance statique, le destructeur s'exécutera après main se termine à un moment donné.
Peu importe, lorsque le processus se termine, toute la mémoire retourne au système d'exploitation.
Comme si souvent, "ça dépend". Dans tout système d'exploitation digne de ce nom, lorsque votre processus se termine, toute la mémoire et les autres ressources utilisées localement dans le processus seront libérées. Il suffit de ne pas besoin de s'inquiéter à ce sujet.
Cependant, si votre singleton alloue des ressources avec une durée de vie en dehors de son propre processus (peut-être un fichier, un mutex nommé, ou quelque chose de similaire), vous devez envisager le nettoyage approprié.
RAII vous aidera ici. Si vous avez un scénario comme ceci:
class Tempfile
{
Tempfile() {}; // creates a temporary file
virtual ~Tempfile(); // close AND DELETE the temporary file
};
Tempfile &singleton()
{
static Tempfile t;
return t;
}
...ensuite, vous pouvez être rassuré que votre fichier temporaire sera fermé et supprimé, mais votre application se termine. cependant, ce n'est pas thread-safe, et l'ordre de suppression d'objet peut ne pas être ce que vous attendez ou avez besoin.
Cependant, si votre singleton est implémenté comme ceci
Tempfile &singleton()
{
static Tempfile *t = NULL;
if (t == NULL)
t = new Tempfile();
return *t;
}
... ensuite, vous avez une situation différente. La mémoire utilisée par votre fichier temporaire sera repris, mais le fichier ne sera PAS supprimé car le destructeur ne sera pas être invoquée.
Vous devez explicitement nettoyer tous vos objets. Ne comptez jamais sur le système d'exploitation pour nettoyer pour vous.
Où j'utilise habituellement un singleton est d'encapsuler le contrôle de quelque chose comme un fichier, une ressource matérielle, etc. Si Je ne nettoie pas correctement cette connexion, je peux facilement fuir les ressources système. La prochaine fois que l'application s'exécute, il peut échouer si la ressource est toujours verrouillé par l'opération précédente. Un autre problème pourrait être que toute finalisation - comme l'écriture d'un tampon sur le disque-pourrait ne se produit pas s'il existe toujours dans un tampon appartenant à une instance singleton.
Ce n'est pas un problème de fuite de mémoire - le problème est plus que vous pouvez fuir des ressources comme d'autres que la mémoire qui peut ne pas être aussi facilement récupérée.
Chaque langue et environnement sera différent, bien que je convienne avec @Aaron Fisher qu'un singleton a tendance à exister pendant toute la durée du processus.
Dans L'exemple de C++, en utilisant un idiome singleton typique:
Singleton &get_singleton()
{
static Singleton singleton;
return singleton;
}
L'instance Singleton sera construite la première fois que la fonction est appelée, et la même instance aura son destructeur appelé pendant la phase de destructeur statique global à l'arrêt du programme.
Comment créez-vous l'objet?
Si vous utilisez une variable globale ou une variable statique, le destructeur sera appelé, en supposant que le programme se termine normalement.
Par exemple, le programme
#include <iostream>
class Test
{
const char *msg;
public:
Test(const char *msg)
: msg(msg)
{}
~Test()
{
std::cout << "In destructor: " << msg << std::endl;
}
};
Test globalTest("GlobalTest");
int main(int, char *argv[])
{
static Test staticTest("StaticTest");
return 0;
}
Imprime
In destructor: StaticTest
In destructor: GlobalTest
Tout type d'allocation, à l'exception de celles des mémoires partagées, est automatiquement nettoyé par le système d'exploitation lorsque le processus se termine. Par conséquent, vous ne devriez pas avoir à appeler explicitement le destructeur singleton. En d'autres termes pas de fuites...
En outre, une implémentation singleton typique comme le Singleton de Meyers est non seulement sécurisée lors de l'initialisation lors du premier appel, mais également garantie de fin gracieuse lorsque l'application se termine (le destructeur est invoquer).
Quoi qu'il en soit, si l'application reçoit un signal unix (c'est-à-dire: SIGTERM ou SIGHUP), le comportement par défaut est de terminer le processus sans appeler les destructeurs des objets alloués statiques (singletons). Pour résoudre ce problème pour ces signaux, il est possible de disposer d'un gestionnaire appelant exit, ou de disposer exit d'un tel gestionnaire -- signal(SIGTERM,exit);
Il est folklorique de libérer explicitement les allocations de mémoire globales avant la fin de l'application. Je suppose que la plupart d'entre nous le font par habitude et parce que nous pensons qu'il est un peu mauvais d ' "oublier" une structure. Dans le monde C, c'est une loi de symétrie que toute allocation doit avoir une désallocation quelque part. Les programmeurs C++ pensent différemment s'ils connaissent et pratiquent RAII.
Dans le bon vieux temps D'AmigaOS, par exemple, il y avait de vraies fuites de mémoire. Lorsque vous avez oublié de désallouer la mémoire, il serait Ne jamais redevenir accessible tant que le système n'a pas été réinitialisé.
Je ne connais aucun système d'exploitation de bureau qui se respecte ces jours - ci et qui permettrait aux fuites de mémoire de sortir de l'espace d'adressage virtuel d'une application. Votre kilométrage peut varier sur les appareils embarqués lorsqu'il n'y a pas de comptabilité de mémoire étendue.
Dépend de votre définition d'une fuite. L'augmentation de la mémoire non liée est une fuite dans mon livre, un singleton n'est pas non lié. Si vous ne fournissez pas de comptage de références, vous gardez intentionnellement l'instance en vie. Pas un accident, pas une fuite.
Le destructeur de votre wrapper singleton doit supprimer l'instance, ce n'est pas automatique. S'il alloue simplement de la mémoire et pas de ressources du système d'exploitation, cela ne sert à rien.
Un singleton serait une instance de l'objet. C'est pourquoi il ne nécessite pas de compteur. Si cela va exister pour la longueur de votre application, le destructeur par défaut ira bien. La mémoire sera, dans tous les cas, récupérée par le système d'exploitation lorsque le processus se termine.
Dans les langages comme C++ qui n'ont pas de garbage collection, il est préférable de nettoyer avant la fin. Vous pouvez le faire avec une classe ami destructeur.
class Singleton{
...
friend class Singleton_Cleanup;
};
class Singleton_Cleanup{
public:
~Singleton_Cleanup(){
delete Singleton::ptr;
}
};
Créer la classe de nettoyage au démarrage du programme, puis à la sortie du destructeur sera appelé nettoyage du singleton. Cela peut être plus verbeux que de le laisser aller au système d'exploitation mais il suit les principes RAII et en fonction des ressources allouées dans votre objet singleton cela peut être nécessaire.
Toute mémoire de tas allouée par votre processus et non libérée (supprimée) sera récupérée par le système d'exploitation. Si vous utilisez l'implémentation la plus courante du singleton, qui utilise des variables statiques, cela sera également nettoyé à la fin de votre application.
* cela ne signifie pas que vous devriez contourner les nouveaux pointeurs et ne jamais les nettoyer.
J'ai rencontré un problème comme celui-ci et je pense que cela devrait fonctionner même si le thread principal se termine en premier et prend les objets statiques avec. Au lieu de cela:
Singleton &get_singleton() {
static Singleton singleton;
return singleton;
}
Je pense
Singleton &get_singleton() {
static std::shared_ptr<Singleton> singleton = std::make_shared<Singleton>();
static thread_local std::shared_ptr<Singleton> local = singleton;
return *local;
}
Ainsi, lorsque le thread principal quitte et prend singleton
avec elle, chaque thread a toujours ses propres local
shared_ptr
qui garde le un Singleton
vivant.