Codons ensemble mes frères
Prenons une hiérarchie de classes assez simple. Disons un CGobelin
et un CDragon
qui dérivent de CMonster
. Chacun implémente la méthode virtuelle pure void Attack()
qui est déclarée dans CMonster
. En plus de cette méthode, chaque classe se définit des données supplémentaires en plus de celles de CMonster
.
Ensuite, quelque part dans votre code, vous ajoutez une méthode vous permettant de récupérer tous les monstres. Comme vous êtes dans un langage assez bas niveau, vous vous dites qu'utiliser l'arithmétique des pointeurs vous ferait gagner un peu de performance. Allez hop, une jolie CMonster* GetMonsters()
. Et vous utilisez alors le pointeur retourné comme un tableau.
Et là, c'est le drame. Tout explose, ou presque.
Jouons ensemble mes frères
Hop, le personnage se balade dans un forêt hantée (CHauntedForest
) et rencontre son premier monstre. Le combat, malgré sa difficulté se passe sans trop de mal, même si vous devrez par la suite allez voir le prêtre du prochain village pour qu'il vous fasse repousser ce membre que vous venez de perdre et dont vous vous servez tant.
Bon le programme fonctionne bien, test terminé... Heu non. Attention. La fameuse méthode devait renvoyer un tableau de monstres, alors vous prenez votre courage à 2 mains (ou votre épée à 2 mains, au choix) et vous y retournez !
Et là, c'est vraiment le drame, votre magnifique quète s'achève ici dans un torrent de coredumps et d'erreurs de pagination. Votre personnage est mort, et votre programme aussi.
Débuggons ensemble mes... Hey ! Mais revenez !
Mais que s'est-il bien passé, vous avez pourtant bien utilisé votre superbe tableau de monstres. Et bien oui, justement, et là est le vrai drame.
L'arithmétique des pointeurs en C et en C++ se base tout simplement sur le type déclaré et est un simple calcul d'octets effectué par le compilateur. Quand vous utilisez votre pointeur sur un CMonster
comme un tableau, le compilateur croit que vous allez parcourir un tableau de CMonster
, et va donc incrémenter votre pointeur en fonction de la taille de CMonster
et pas des types réels des objets pointés.
Ce qui fait que votre 1er objet sera toujours correcte, puisque vous y accédez avec le bon pointeur. Mais pour le 2e, c'est autre chose. Votre pointeur subit un offset correspondant à la taille d'un CMonster
avec que vos objets sont des CDragon
s. Résultat : votre 2e objet pointé est en fait un infâme mutant à cheval entre 2 objets réels de votre tableau, mutant dans le sens où ses données ne sont pas du tout correctes, infâme, dans le sens où sa table des méthodes virtuelles est complètement fausse, entrainant sans aucun doute une mort rapide et sans douleur de votre programme au prochain appel de l'une de ces méthodes.
Moralité, on ne rappellera jamais assez que la performance ne doit pas être votre priorité lorsque vous développez. Il aurait été ici plus sage d'utiliser des méthodes d'accès aux données qui soient plus sûres comme un CMonster* GetAt(int32 nIndex)
.
Même si cette approche est plus lente, il sera toujours temps de modifier ces quelques détails lorsqu'un éditeur vous aura remis un contrat de plusieurs millions d'euros parce que votre jeu est « marrant, très stable, mais un peu lent ».
1 De Batmat -
Argh, plusieurs fautes et des fois pas les mêmes fautes aux mêmes mots :)
* Mais que c'est-il bien passé => s'est
* 1re objet sera toujours correcte => oooh un doublé. À vouloir bien écrire 1re, tu as oublié qu'un objet est masculin :) => 1er (ou comme tu veux, mais pas 1re) et correct.
* données ne sont pas du tout correct => correctes
* entrainant => j'ai un doute, ça prend pas un circonflexe ?
La bise :)
2 De Vincent -
Merci Batmat, c'est corrigé :)
Pour le « entrainant », il y a bien un accent, mais comme il ne lève aucune ambiguité, je ne le mettrais pas :D
Le poutou :D
3 De François Parmentier -
Je peux jouer dites? Et on a le droit de corriger les commentaires (je sais c'est vache) ?
A priori, il y a un tréma sur le i de ambiguïté.
Et puis, quand tu dis "je ne le mettrais pas", c'est du conditionnel ou du futur de l'indicatif?
Moi je penche pour du futur, et j'aurais donc écrit "je ne le mettrai pas".
Bon, je retourne jouer dans mon coin, en priant que je n'aie point fait de faute de français dans ce commentaire. ;)
4 De Vincent -
Héhé, bien joué François :)
5 De Alexis -
Hop, encore deux fautes :)
"Disons un CGobelin et un CDragon qui dérive de CMonster." => "dérivent"
"vous vous dites qu'utilisez l'arithmétique" => "utiliser"
Sinon, pour le contenu, je dirais que, comme toujours en C++, il faut y aller doucement (souvent casse-geule de jongler avec des pointeurs :). En effet, s'il n'y avait eu qu'un seul monstre dans le jeu, on aurait fait une fonction CMonster * GetMonster() retournant un pointeur sur l'objet, mais pas CMonster GetMonster() car on aurait copié l'objet (possiblement appel du constructeur de copie).
Pour un tableau de monstres, du coup, il faut bien rajouter une étoile et on obtient CMonster ** GetMonsters().
En passant, on n'aurait pas eu ce problème dans un langage plus récent de type Java ou C# : CMonster GetMonster() et CMonster [ ] GetMonsters(). Dans ces langages, seuls les types primitifs sont passés par valeur, tous les objets étant passés par référence.