Je n'avais pas une grande expérience du C++ pendant les années où j'ai travaillé sur mon projet d'OS, mais j'avais suivi avec intérêt les investigations de DasCandy pour parvenir à faire fonctionner les objets globaux (cf. http://wiki.osdev.org/C_PlusPlus). Et j'en avais retenu une chose : n'utiliser ces choses qu'en cas d'urgence, et avec la prudence qui s'impose quand on manipule de la nitro.
D'un autre côté, pour pouvoir définir le comportement de mon appleman, il me faut beaucoup plus de
contrôleurs, qui sont appelés en séquence pour définir le comportement réel pour un état. Au moment de la lecture du script qui définit le niveau, je vais donc traiter une série de lignes "using tracker(Bilou)" "using Momentum(5,-1)" "using walker()", chacune de ces lignes donnant le nom d'une classe de contrôleur à instancier. J'ai donc pour chaque classe dérivée de iGobController une classe dérivée de iGobFactory capable de construire le bon objet à partir de la chaîne de caractères entre parenthèses.
So i've started decomposing the former "big chunk" controller such as 'walk' and 'gravity' into 'micro-gob-controllers' and make sure they are published in the std::map used by GameScript to prepare the GobState objects according to the rules found in the script, such as "using tracker(Bilou) ; using Momentum(5,1)", "using walker". The corresponding output is a chain of controllers that each do a specific job, easing code reuse. The BerryBat and the Appleman can for instance share the same "tracker" controller that tell them where Bilou stands, while Bilou and the appleman share "Momentum" and "walker" controllers, but Bilou receives its input from "dpad" rather than from "tracker".
Bon. Mais il faut que ces
factories -- qui sont du coup des
singletons -- soient enregistrées dans une map pour que le script les retrouve (c'est pas du Java: exit le 'Class.forname("tracker")'). Et je voulais pouvoir ajouter encore et encore des contrôleurs à mon code sans devoir maintenir une grosse fonction du genre "register_all_controllers". Cyril proposait donc évidemment des objets globaux (qu'on appelle parfois aussi "objets statiques"), mais je n'étais pas chaud: avec eux, il y a toujours anguille sous roche ...
Comme je n'avais pas d'argument vraiment convaincant, j'ai essayé quand-même. Première surprise : les "printf" ne marchent plus de manière fiable! J'ai encore quelques messages imprimés quand j'utilise "printf" sans aucun argument, mais c'est tout... un problème que j'avais déjà rencontré.
Un peu de désassemblage, et je constate que les messages qui sont effectivement imprimés n'utilisent pas véritablement printf: ils ont été convertis en interne en
puts. Conclusion: en appelant
printf dans l'initialisation des objets globaux, qui a lieu avant
ConsoleInit(), j'ai empêché l'initialisation correcte de la console et rien ne va plus.
I stumbled upon two issues while implementing those factories -- which i initially chose to build with static objects. The first problem is a "known issue" of my libnds version: if i ever invoke a printf() function before consoleInit(), i screw up the stdlib's internal state and all subsequent prints using this function won't output a single pixel >_<.
Je change d'approche: initialisation "en aveugle", et une petite méthode "test" indépendante, histoire de vérifier que mes contrôleurs sont bien présents, appelée juste avant la boucle principale du programme. Deuxième surprise: il y a bien des contrôleurs enregistrés mais pas tous. Pourtant, mon code est bien compilé, tel que je l'ai écrit. Et il avait été appelé, puisqu'il était capable de foutre en l'air l'affichage...
La map est une variable de classe, statique, initialement vide. "As plain as can be" ...
... statique ? ...
Eh là, une minute ...
Et comment le compilateur sait-il que parmi ces objets statiques, la bonne initialisation des GobFactories dépend du fait que la map ait déjà été initialisée ? Bin il ne le sait pas, évidemment. Le code d'initialisation appelle tous les constructeurs dans un ordre qui lui convient, et si ça ne me plaît pas,
c'est tant pis pour ma faute.
Once this was fixed, i realised that i was f00lish to use static objects to register things in a static map. When i say "static", here, i actually mean "some object defined at the top level that should be instanciated before main() is ever called". The problem with such object is that their initialization order is undefined. In other words, my factory F could very well inserts itself in the map and then the map itself initializes (and thus clears what F modified).

Retour temporaire à "register_all_controllers" pendant que j'évalue les options... Voilà qui donne un nouvel éclairage au dicton "
In C++ it's harder to shoot yourself in the foot, but when you do, you blow off your whole leg." (Stroustrup himself ;)