Salut,

pour moi, le singleton ne s'implémente pas comme ça. Pour moi l'idée, c'est:

- le constructeur devient privé, on ne peut pas l'appeler en dehors de la classe. - une méthode public ( GetInstance() ) est ajouté à la classe et un membre privée est ajouté lui aussi qui est un pointeur vers une instance.
Exemple:

class ClassA {

private:
   ClassA * instanceA;

   ClassA(...);
public:
   getInstance()
};

ClassA::ClassA(...)
{
   instanceA = NULL;
   ...
}

ClassA::getInstance()
{
   if (instanceA == NULL) {
      instanceA = new InstanceA();
   }
   assert(instanceA != NULL);
   return instanceA;
}
Si on utilise ça sur la classe Font, c'est un peu différent puisqu'on a plusieurs instances différentes.

L'accès à small_font devient par exemple:
Font::SmallFont();

On peut alors faire un
Font::SmallFont()->Render(...);

Ceci permet de garder exactement les mêmes #include qu'avant et évite de créer un fichier global. De plus, je trouve ça plus propre perso.

Matt

Victor Stinner a écrit :

Salut,

*** Avant toute chose : mettez à jour votre dépôt SubVersion local, j'ai changé un paquet de fichiers !!! ***

*** Email un peu long mais c'est pour expliquer en profondeur un problème de Wormux vieux de 4 ans au moins ***

J'ai enfin fait le grand pas : j'ai écrit la classe qui va gérer des singletons. En deux mots, c'est des instances qui vont être créer lorsqu'on y accède. L'avantage est qu'on contrôle la création de l'instance, et qu'on peut ajouter des bouts de code (ex: vérifier que SDL est lancé avant de charger une image!). D'ailleurs j'ai trouvé une erreur: ImagesParSeconde instanciait un objet Text qui a besoin d'une Font qui était créée ... avant l'initilisation de SDL et TTF !!! (mais apparement, ça marchait quand même :-P).

J'ai donc supprimé toutes les variables globales big_font, small_font, etc. et j'ai remplacé ça par : global().small_font() (il faut un #include "../include/global.h").

Le désavantage est que ça ajoute deux déréférencement de pointeur et deux if pour chaque accès aux variables (un if pour être exact, l'autre est une assertion => désactivée en mode non debug). Bon, je pense que ça coûte rien du tout au niveau des performances et vous verrez après (dans mon email) tous les avantages.

L'autre soucis est que si on met tout ce qui doit être instancié dans "Global", la compilation d'un seul fichier demandera l'ensemble des fichiers d'entêtes (.h) ... ce qui risque d'être plutôt lourdingue (ralentir la compilation quoi). Une solution plus soft serait de faire une classe "Global" pour chaque type d'objet (ex: Font, Graphic, Game, ... enfin, il faudrait en discuter).

À terme, la majorité des variables globales devraient utiliser des singletons (je pense). Par contre, pour certains classes "critiques", c'est peu/pas envisageable. Je pense notamment à Config qui est utilisé à peu près partout dans le jeu ...

---

Quelques problèmes résolus avec les singletons :

- Le problème des dépendences sont supprimés. Je m'explique : avant, tout était en variable globale. Le gros problème est que les objets sont instanciés l'un après l'autre. Mais que se passe-t-il lorsque A utilise B alors que B n'est pas encore instancié ? Ben le compilateur ne dit rien, et souvent ça passe. Mais ça arrive que ça plante ! Exemple : en modifiant Config::Config() pour ajouter ttf_filename, j'ai utilisé DEFAULT_DATADIR. Mais ... ça plantait lamentablement du côté de std::string !? Après une bonne heure j'ai compris le problème : DEFAULT_DATADIR n'existait pas encore ! Gloups ! J'espère que vous voyez le problème. J'ai rencontré ce problème très très souvent. Un autre au hasard : Time::Time() accédé avant sa création (renvoyait n'importe quoi donc).

- On contrôle enfin quand l'objet est détruit. Avant, les bibliothèques étaient désactivées (SDL_Quit & Cie, et avant l'équivalent pour ClanLib), et seulement après les objets étaient détruits. Le problème est que la destruction a besoin de la bibliothèque (ex: l'informer de la destruction de l'objet). Et fatalement : ça plante. J'ai eu ce problème très récement : Font::~Font() qui appelait TTF_CloseFont() *après* TTF_Quit(). (*bug corrigé*)

Hum, il doit y avoir d'autres avantages, mais je ne vois pas trop là tout de suite.

J'espère que je vous ai convaincu de l'utilité de mon dernier patch de 1000 lignes :-P

Victor


Répondre à