web-dev-qa-db-fra.com

C++ singleton vs objet complètement statique

Disons que nous n'avons besoin que d'une instance de classe dans notre projet. Il y a plusieurs façons de le faire.

Je veux comparer. S'il vous plaît pouvez-vous revoir ma compréhension.

1) Motif Singleton classique

2) Classe complètement statique (toutes les méthodes et tous les membres sont statiques).


Si je comprends bien, les différences sont les suivantes:

a) L'ordre d'initialisation des membres statiques sur différentes unités n'est pas défini. L'initialisation complètement statique des membres ne peut donc utiliser aucun membre/fonction statique provenant d'autres modules. Et singleton n'a pas ce problème.

b) Nous devons gérer le threading pour getInstance () de Singleton. Cependant, la classe complètement statique n'a pas ce problème.

c) L’accès aux méthodes est un peu différent. Foo :: bar (); vs Foo :: getInstance () -> bar (); En règle générale, singleton peut renvoyer NULL pour indiquer qu'il y a eu des problèmes de construction d'objet et que la classe statique ne le peut pas.

d) La définition de classe semble un peu maladroite avec un tas de statique pour la classe statique.

Ai-je manqué quelque chose? 

32
Victor Ronin

Que vous l'appeliez Singleton ou Monostate ou tout autre nom de fantaisie ... la nature très agaçante de cela est que vous n'avez qu'une seule instance de l'objet et que vous y écrivez: les variables globales, quelle que soit leur apparence, sont mal .

L'idée que vous besoin une instance unique est généralement maladroite. La plupart du temps, ce dont vous avez réellement besoin, ce sont des parties qui communiquent partagent la même instance. Mais un autre groupe de pièces pourrait parfaitement utiliser une autre instance sans problème.

Tout code qui prétend à besoin une variable globale est très suspect. Cela peut paraître plus simple d’en utiliser une, mais avouons-le, vous pourriez parfaitement transmettre l’objet à chaque fonction, cela compliquerait leur signature, mais cela fonctionnerait néanmoins.

Cependant, j'avoue qu'il semble plus simple d'utiliser des variables globales ... jusqu'à ce que vous remarquiez les problèmes:

  • multithreading est compromis
  • (testabilité} _ est réduit, puisqu'un test peut affecter celui qui le suit
  • l'analyse de dépendance est extrêmement compliquée: il est difficile de savoir dans quel état dépend votre méthode lorsque vous extrayez global à partir de sous-méthodes ...

Maintenant, en ce qui concerne singleton, la création multithread n'est pas utilisable en C++ avant C++ 0x (quand cela devient possible en utilisant des locales statiques), vous devez donc le créer dans un seul thread et différer l'accès avant: instanciez-le dans main , c'est votre meilleur pari.

La destruction peut causer du chaos, car la vie du Singleton/Static peut prendre fin avant que d'autres n'en aient fini, ce qui entraîne un comportement indéfini. Ceci est typique d'un Logger singleton. La stratégie habituelle est de fuir sans vergogne ...

Après cela, si vous en voulez toujours un, je vous souhaite bonne chance, c'est tout ce que cette communauté peut faire pour vous.

20
Matthieu M.

Une autre option que vous oubliez est celle de namespace.

namespace xyz {
namespace {
    int private_variable;
}

int get_pv() {
    return private_variable;
}
}

Sur le plan fonctionnel, cela ressemblera à votre option n ° 2, mais vous ne pouvez pas "supprimer" accidentellement cette chose. Vous ne pouvez pas créer accidentellement une instance de celui-ci. Il ne s'agit que d'une collection de données et de fonctions connexes accessibles globalement. Comme dans mon exemple, vous pouvez même avoir des membres et des fonctions "privés".

Bien sûr, l'utilisation ressemblerait à ceci:

int x = xyz::get_pv();
9
Evan Teran

Disons que nous n'avons besoin que d'une instance de classe dans notre projet. Il y a plusieurs façons de le faire.

Une meilleure solution:

Une variable principale que vous transmettez en tant que paramètre à toutes les fonctions requises en serait une autre.

a) L'ordre d'initialisation des membres statiques sur différentes unités n'est pas défini. Ainsi, l'initialisation complètement statique des membres ne peut utiliser aucun membre/fonction statique provenant d'autres modules. Et singleton n'a pas ce problème.

Les singletons ont ce problème si leur constructeur/destructeur accède à une autre variable de durée de vie statique globale.

b) Nous devons gérer le threading pour getInstance () de Sigleton. Cependant, la classe complètement statique n'a pas ce problème.

Ce n'est pas vraiment un problème, n'est-ce pas? Si vous en êtes conscient, ajoutez simplement les verrous appropriés dans le code.

c) L’accès aux méthodes est un peu différent. Foo :: bar (); vs Foo :: getInstance () -> bar (); Généralement, sigleton peut renvoyer NULL pour indiquer qu'il y a eu quelques problèmes de construction d'objet et que la classe statique ne peut pas.

Je voudrais que mon getInstance () renvoie une référence. Ensuite, il n'y a pas d'ambiguïté sur si le pointeur est NULL. Cela a fonctionné ou a jeté une exception. Cela conduit également à une conception où la destruction est correcte appelée sur l'instance (ne prenez pas cela comme conseil pour utiliser Singleton, je l'éviterais si possible (mais si vous l'utilisez, rendez-le ordonné)).

d) La définition de classe semble un peu maladroite avec un tas de statique pour la classe statique.

Pas plus bruyant que d'écrire un singleton correctement.

Le problème avec ces deux méthodes est qu'elles accèdent toutes les deux à global mutable state et que, par conséquent, l'utilisation de ces objets 'à instance unique' par d'autres objets est masquée pour l'utilisateur. Cela peut entraîner des problèmes lors des tests (TDD requiert la capacité de simuler des fonctionnalités externes, mais global mutable state empêche le testeur de simuler (facilement) des dépendances externes).

Tout objet qui n'est pas POD a un constructeur qui peut potentiellement lever une exception. Ainsi, pour les objets de l’espace de noms global, cela signifie que des exceptions peuvent être levées avant que main () ne soit entré (ceci peut rendre difficile la recherche de bogues (si vous avez beaucoup d’objets globaux (vous devez placer des points d’arrêt partout)). le même problème existe avec un singleton qui est évalué paresseusement; si, lors de la première utilisation, il lance, comment le corrigez-vous afin d'éviter une tentative ultérieure? Votre application continuera-t-elle à être lancée chaque fois que le singleton sera récupéré?

1
Martin York

Vous pouvez ajouter: les objets statiques peuvent générer des exceptions. L'exécutable ne démarrera pas et il est difficile de bien déboguer/gérer.

0
Jay

Vous pouvez également utiliser le modèle Borg, qui est un peu plus compliqué en C++, sauf si vous avez accès à une classe de pointeur partagée. L'idée est que n'importe quel nombre de classes Borg peuvent être instanciées, mais leur état est partagé par toutes les instances.

0
Colin