.-- -- ---- ----. ___| MKD1001\002 |____ _ ___________ _ ___________________________________ | '---------------' | | | | .---- - ----------------- - ---------------------------------- - --. . '-----' '---' Da Singletons AKA Global Variables ... evil? Foreword: avant de commencer, j’aimerais préciser que la qualité d’écriture de cette article est complément merdique et je m’en excuse. C’est la première chose que j’écrit en français depuis les 5 dernières années mais anyway, je me suis efforcer de spellchecker avec Word et peu être la faire passer a une couple de personne avant de la publier. Itou, le code écrit ici na jamais été compiler, je l’ai écrit from the top of my head sans me soucier des syntax errors (c’est juste du code d’exemple anyway, c’est pas supposer marcher ou faire rien d’utile. Baser vous dessus). Si vous voulez des exemples qui marchent, downloader 9th Life sur le SVN de Mindkind-Coding si ça existe encore (ou il doit avoir des links sur http://www.ogre3d.org ou http://sourceforge.net/projects/ninthlife/ ), c’est plein de singleton la dedans. Lets get started Ok ici je vais vous conter une petite histoire sur les variables global ou anyway quelque chose du genre. Background required: C++ basic classes/inheritance, static function, reference, some coding ability. A qui cette article est fait pour: du monde qui code en C++ ? ça m’étonnerais que les h4x0r d00dz aie de quoi d’intéressant a lire ici. Bon un singleton c’est quoi? De ce que je me suis fait dire, c’était la façon en java pour faire des variables globaux. Bon la je voie tout les ptit programmeur crier se que leur prof leur a dit back in collège. « Global variables are evil !! evil !!!! Si jamais vous en utiliser une… vous ne méritez pas de jamais écrire de code pour personne me si c’est du IRC scripting ou du VB… Evil!!!!” En vrai ce n’est pas si « evil » que ça si on les utilise comme du monde. Apparemment, c’est même plus rapide et ça vous enlève la misère de mettre 56 arguments à vos function pour avoir accès a des variables qui sont seulement disponible plus bas dans votre programme. La raison pourquoi c’est evil, c’est parce que on pourrais tout mettre global et se perde complément vue que tout vos variables sont dans le même scope et vous allez avoir « x1,x2,x3,x4,y1,y2,y3,y4 » et ça ne finira plus. Les singletons vous apprennent un peu a faire de l’ordre dans vos variables globaux. De mon bord, j’ai appris les singletons en faisant du gamedev ou on finit toujours par développer cinquante-six mille systems et tout vire en shit parce qu’il y a 32 différent types de character et 32 types d’item et plein de data qui a besoin d’être accéder un peu partout. So, un singleton acte comme 2 choses. Un manager et un accès global au data qu’il manage. Je vais commencer par vous montrer un peu comment on fait un singleton. Au cours du temps, j’ai vue différente façon de les faire et ici, je vais vous montrer celle que j’utilise. Ma technique consiste d’avoir une function static d’une class qui retourne un référence a la class. (wtf !?!?!). Ok sa sonne peut-être comme quelque chose qui sort d’un livre d’instruction de chez IKEA mais dans le fond, ce n’est pas très compliquer. Une function static d’une class. C’est quoi ca fait? CODE Time!!!! class ObjectCloseAuditAlarm { public: Object(); // constructor ~Object(); //destructor static void FunctionStatic(); void FunctionPasStatic(); private: int m_meber1; char m_member2 }; Bon voila une petite class qui ne fait rien mais qui a une function static et une function pas static. D’habitude vous devez créer une instance de la class pour utiliser leur functions. void main() { Object unObject; unObject.FunctionPasStatic(); } Bon, on s’entend jusque la? Good. La function static n’a pas besoin d’instance. void main() { Object::FunctionStatic(); } Et le contenu de la function va se faire exécuter. So dans le fond, c’est une function « global ». Maintenant un exemple de singleton. class Singleton { public: //GENERAL ~Singleton(); //destructor static Singleton &Instance(); //ACCESSOR int GetMember1() const { return m_member1; } char GetMember2() const { return m_member2; } //MANIPULATOR void SetMember1(int member1) { m_member1 = member1; } void SetMember2(char member2) { m_member2 = member2; } private: int m_meber1; char m_member2; Singleton(); // constructor }; Singleton &Singleton::Instance() { static Singleton instance; return instance; } Bon qu’es-ce qu’on a ici. Une class avec un constructeur… private ?!?!? Oui, ce n’est pas une erreur. Mais je vais expliquer pour quoi plus tard. Dans notre cas, on pourrait avoir un constructeur public et tout fonctionnerait mais j’ai mis mon constructeur priver juste parce que, c’est la façon que je le fait. La partie intéressante, c’est la function static Instance. Comme vous voyez, elle est static et retourne une référence a ça propre class. Dans la définition de la function, on créer une instance static de notre class et la retourne. (Instance static parce que si la function a déjà été appelé, on ne veut pas re-créer l’object) L’idée est que Instance() va créer l’object si elle n’est pas créer et nous donner un instance a la class. So en code. void main() { Singleton::Instance().SetMember1(10); Singleton::Instance().SetMember2('a'); std::cout << "member1: " << Singleton::Instance().GetMember1() << " member2: " << Singleton::Instance().GetMember2() << std::endl; } Et voila. On a modifier member 1 et member 2 sans même avoir a créer un instance de la class (ou anyway, on a rien vue de ça) et oui, believe it or not, vous pouvez accéder ces members la n’importe ou dans votre programme ou vous faite #include le header file (.h) qui contient la class déclaration du singleton. Maintenant un exemple real life. Imaginer vous que vous créer une jeux de combat. Il va avoir 18 characters avec des propriétés similaires mais dans le fond. C’est juste des characters. Il marche, il saute, il meurt. So vous aller créer une base Character class et des class dériver pour chaque character. Les characters vont bien sure « interacter » entre eux pendant l’exécution du jeux a différent endroits dans le program. Dans ce cas, nous allons créer une « CharacterManager » qui va se charger de tout nos characters. De cette façon, nous allons être capable accéder n’importe quel character a n’importe quelle endroit dans le jeux. (Je vais essayer de garder les détails aussi minime que possible pour ne pas vous mêler. N’essayer pas de comprendre ce que les functions de la character class font. Se sont juste des functions et ça pourrais être n’importe quoi. Pré occuper vous plus sur se que le CharacterManager va faire avec les class) //character base class class Character { public: //GENERAL Character(); //constructor virtual ~Character(); //destructor virtual void Init(); //init everything virtual void Terminate(); //clean up virtual void Logic(); //called every frame in the main Game loop void Walk(); void Jump(); virtual void Attack(); virtual void SpecialMove()=0; //ACCESSOR int GetWalkingSpeed() const { return m_walkingSpeed; } int GetHealth() const { return m_health; } int GetAttackPower() const { return m_attackPower; } const std::string &GetName() const { return m_name; } //MANIPULATOR void SetWalkingSpeed(int walkingSpeed) { m_walkingSpeed = walkingSpeed; } void SetHealth(int health) { m_health = health; } void SetAttackPower(int attackPower) { m_attackPower = attackPower; } void SetName(const std::string &name) { m_name = name; } protected: int m_health; //health of our character int m_walkingSpeed; //the speed that the character is walking int m_attackPower; //how strong the character is std::string m_name; //the name of the character }; Here’s the base character class that basically setup the basic properties of a character (again, we’re staying in the basic) but it is sort of what you would expect. class CharacterNinja public Charactcer { public: CharacterNinja(); ~CharacterNinja(); void Init(); //init everything void Terminate(); //clean up void Logic(); //called every frame in the main Game loop void Attack(); void SpecialMove(); private: SpecialProperties m_specialProperties; }; class CharacterKarateKid public Charactcer { public: CharacterKarateKid(); ~CharacterKarateKid(); void Attack(); void SpecialMove(); private: SpecialProperties m_specialProperties; }; Then 2 derive class that shows what you would do with your character class enum CharacterIndex { CHARACTER_NINJA = 0, CHARACTER_KARATEKID = 1, }; Ceci est juste une enum qui est une bonne practice pour indexer nos élément. Ou vous pouvez toujours utiliser une map et les mapper a une string. (Up to you) class CharacterManager { public: //GENERAL ~CharacterManager(); static CharacterManager &Instance(); void Initialise(); void Terminate(); void Logic(); //ACCESSOR Character *GetCharacter(int index) const { return m_characterList[index]; } private: std::vector m_characterList; CharacterManager(); }; CharacterManager::CharacterManager() { m_characterList.push_back(new CharacterNinja()); m_characterList.push_back(new CharacterKarateKid()); } CharacterManager::~CharacterManager() { for (int i=0; i<(int)m_characterList.size(); i++) delete m_characterList[i]; } void CharacterManager::Initialise() { for (int i=0; i<(int)m_characterList.size(); i++) m_characterList[i].Init(); } void CharacterManager::Terminate() { for (int i=0; i<(int)m_characterList.size(); i++) m_characterList[i].Terminate(); } void CharacterManager::Logic() { for (int i=0; i<(int)m_characterList.size(); i++) m_characterList[i].Logic(); } Bon voila la partie importante. Ca démontre comment le manager va manager les characters de façon élégante. Il va se charger seulement de la base class et s’assurer que tout les characters sont traité de façon égale. Maintenant je vais vous donner un exemple de se que ça a l’air dans du code. void main() { CharacterManager::Instance().Initialise(); //main game loop while (gameNotOver) { //logic CharacterManager::Instance().Logic(); //input if (PlayerInput1Walk) { CharacterManager::Instance().GetCharacter(CHARACTER_KARATEKID)->Walk(); } if (PlayerInput2Walk) { CharacterManager::Instance().GetCharacter(CHARACTER_NINJA)->Walk(); } } CharacterManager::Instance().Terminate(); } Ceci est un exemple très simplifier mais ça devrais vous donner une petite idée de comment ça marche. Et ça rend le code assez clean quand on a une 20e de system du genre. SpellManager, ItemManager, MonsterManager, etc. private constructor Bon si vous vous rappeler, j’avais mentionner que j’allais revenir au constructeur priver. Pourquoi ? bien un singleton est fait pour en avoir juste 1 (single). Vous ne voulez pas donner la possibilité au user de créer 50 instance. Sinon vous allez vous ramasser avec 5 fois le même manager se qui veux dire 50 fois les character dupliquer. So c’est une simple technique pour empêcher ça de se produire. Bien sure on peu toujours faire des copy mais si on peu bloquer 60% des attente, c’est déjà mieux que rien. Conclusion Bon aucune idée si cet article a fait du sens pour vous. J’ai p-e mal expliquer le concept mais en bout de ligne, utiliser des singletons comme object factory ou object manager est une façon très élégante de gérer c’est object. En même temps, on peu s’assurer que n’importe quel object qui est créer va être deleter a la fin du programme se qui élimine les memmory leaks. .-----. .---. | '----- - ------ - ------- - ------------------------------------- -' | | | |______ _ ________________________________.--------------------.______ _ ____| | by Gonzo | '--------------------'