EDIT: Désolé, ma question n’est pas claire. Pourquoi les livres/articles préfèrent-ils la mise en œuvre n ° 1 à la mise en œuvre n ° 2?
Quel est l'avantage réel d'utiliser un pointeur dans l'implémentation de la classe Singleton par rapport à un objet statique? Pourquoi la plupart des livres préfèrent-ils cela
class Singleton
{
private:
static Singleton *p_inst;
Singleton();
public:
static Singleton * instance()
{
if (!p_inst)
{
p_inst = new Singleton();
}
return p_inst;
}
};
sur ceci
class Singleton
{
public:
static Singleton& Instance()
{
static Singleton inst;
return inst;
}
protected:
Singleton(); // Prevent construction
Singleton(const Singleton&); // Prevent construction by copying
Singleton& operator=(const Singleton&); // Prevent assignment
~Singleton(); // Prevent unwanted destruction
};
pourquoi les livres/articles préfèrent-ils la mise en œuvre n ° 1 à la mise en œuvre n ° 2?
Parce que la plupart des articles décrivant l'anti-motif Singleton ne comprennent pas parfaitement tous les dangers cachés lorsque vous essayez de l'implémenter en toute sécurité en C++. C'est étonnamment difficile de bien faire les choses.
Quel est l'avantage réel d'utiliser un pointeur dans l'implémentation de la classe Singleton par rapport à un objet statique?
L'utilisation d'un pointeur, avec new
mais pas delete
, garantit que l'objet ne sera jamais détruit. Il n'y a donc aucun danger d'y accéder après la fin de sa durée de vie. Combiné à la création "paresseuse", lors de son premier accès, cela garantit que tous les accès se font sur un objet valide. Les inconvénients sont que la création n'est pas thread-safe et que l'objet et les ressources qu'il acquiert ne sont pas libérés à la fin du programme.
À l'aide d'un objet statique local, la création est thread-safe sur tout compilateur prenant en charge le modèle de threading C++ 11; De plus, l'objet sera détruit à la fin du programme. Toutefois, il est possible d’accéder à l’objet après sa destruction (par exemple, à partir du destructeur d’un autre objet statique), ce qui pourrait entraîner de vilains bugs.
La meilleure option consiste à éviter autant que possible les données statiques et les données globalement accessibles. En particulier, n'utilisez jamais l'anti-motif Singleton; il combine des données globales et statiques avec des restrictions d'instanciation étranges qui rendent les tests inutilement difficiles.
La deuxième version (utilisant une variable statique locale) présente des avantages significatifs.
Il ne nécessite pas l'utilisation de la mémoire libre et ne sera donc pas détecté comme une fuite de mémoire. Il est thread-safe (dans C++11
). C'est plus court et plus simple.
Le seul inconvénient est qu'il est impossible de le rendre portable threadsafe (pour les compilateurs antérieurs à C++ 11) et qu'il ne vous donne pas la possibilité de détruire explicitement l'instance singleton.
Je toujours préférerais le second, mais le premier présente quelques avantages potentiellement intéressants: -
Clarté - la vérification du pointeur comme étant nul est en réalité Ce que le compilateur fait sous le capot lors de la construction d'objets statiques . Du point de vue de «l'apprentissage», il est instructif de Comprendre ce qui se passe lorsque vous utilisez des objets statiques dans la portée de la méthode .
Allocation paresseuse - dans le premier cas, l'objet Singleton est Attribué par tas. Si votre fonction ne s'exécute jamais, l'objet n'est jamais construit Et ne consomme jamais de mémoire. Mais, dans le second cas, l'éditeur de liens attribue à la mémoire Le maintien de l'objet avant le début du programme , Même si la «construction» est paresseuse.
Le second a une destruction non déterministe. Le premier, vous contrôlez le moment où vous supprimez le pointeur, le cas échéant.
La première construction n'est pas thread-safe, bien sûr, mais peut être faite avec boost::call_once
(ou std::call_once
si disponible)
La deuxième construction était assez commune pour que de nombreux compilateurs le rendent sûr pour les threads, même si techniquement, il ne l’est pas (bien que, selon la norme, l’objet ne devrait être créé qu’une fois, je ne suis pas sûr de l’affichage de la norme à la fin de la construction avant qu'un autre thread l'utilise).
S'il n'y a pas de problème avec l'ordre de destruction, vous pouvez continuer et utiliser la version statique, à condition que votre compilateur le garantisse comme thread-safe.
L'un des avantages est que vous n'avez pas à vérifier si le singleton a déjà été instancié.
Une autre solution est que vous n’avez pas à vous soucier de la dé-allocation de mémoire.
Le deuxième exemple porte le nom "Meyers 'Singleton", car il a été publié en premier dans "Effective C++" ou "More Effective C++". Je ne sais pas lequel, mais les deux ont été publiés après "Design Patterns" - de sorte que le Gang of Four aurait tout aussi bien pu ignorer le second motif au moment de la rédaction de leur livre.
En outre, la première approche est beaucoup plus standard pour les autres langues - vous pouvez faire la première en Java ou en C #, mais pas la seconde, de sorte que les personnes venant d'horizons différents peuvent être une autre raison de la renommée de la première.
Sur le plan technique, avec la première approche, vous pouvez contrôler le moment où le singleton est détruit, mais cela pourrait également vous apporter beaucoup de maux de tête.