Petite note à mon blog suite à la découverte du mot clef mutable en C++. Oui, même après 6 ans de développement, je continue de découvrir des trucs qui visiblement font pourtant partie de la base :) C’est plutôt cool de bosser enfin sur des projets développés par des têtes ! Mais trêve de blablah, explication !
On connait tous le mot clef const. Il est là pour garantir qu’un objet ne sera pas altéré / modifié. Un des exemples le plus courant concerne les “getter”, ces petites fonctions souvent inline qui permettent d’accéder à une variable membre en lecture seule. Par exemple :
class SceneNode
{
public:
Matrix GetAbsoluteTransformation(void) const
{
// root node
if (m_Parent == NULL)
return m_RelativeTransformation;
return m_Parent->GetAbsoluteTransformation() * m_RelativeTransformation;
}
private:
Matrix m_RelativeTransformation;
SceneNode * m_Parent;
};
Ici, const garantit que GetAbsoluteTransformation ne modifie pas le contenu de l’instance de SceneNode. Ce qui est parfaitement logique et sain conceptuellement parlant.
Imaginons maintenant que l’on veuille optimiser un peu tout ceci en utilisant un cache de la matrice : en gros, on stocke en interne le résultat du calcule et tant qu’il n’y a pas de transformation, on se contente de renvoyer le résultat. Par exemple :
class SceneNode
{
public:
const Matrix & GetAbsoluteTransformation(void) const
{
if (m_NodeFlags & RECALC_ABSOLUTE_TRANSFORMATION)
{
if (m_Parent == NULL)
m_CachedAbsoluteTransformation = m_RelativeTransformation;
else
m_CachedAbsoluteTransformation = m_Parent->GetAbsoluteTransformation() * m_RelativeTransformation;
// remet le flag a 0 pour ne pas avoir à recalculer au prochain appel
m_NodeFlags &= ~RECALC_ABSOLUTE_TRANSFORMATION;
}
return m_CachedAbsoluteTransformation;
}
private:
Matrix m_CachedAbsoluteTransformation;
Matrix m_RelativeTransformation;
SceneNode * m_Parent;
};
En gros, à chaque fois qu’une modification est apportée à la transformation d’un noeud (que ce soit le parent ou le noeud courant) on active un flag qui indique à la fonction GetAbsoluteTransformation de recalculer la matrice. Et quand ce flag est nul, on renvoie simplement le résultat.
Le problème qui se pose ici (hormis le fait que le code ci-dessus ne compilera très certainement pas à cause du const) c’est que l’on a une fonction qui, conceptuellement, ne modifie pas le contenu de l’instance, mais dans la pratique, si ! En effet, quand on recalcule le cache, on ne modifie pas le contenu logique de la classe : on ne modifie pas sa position, sa rotation, le contenu de ses différents états. MAIS on modifie quand même une variable membre, et donc le compilateur fait chier et on doit virer le const !
Et c’est là qu’intervient notre mot clef mutable : il permet d’autoriser la modification d’une variable membre par une méthode const ! Et notre exemple précédent devient :
class SceneNode
{
public:
const Matrix & GetAbsoluteTransformation(void) const
{
// le contenu ne change pas
}
private:
mutable Matrix m_CachedAbsoluteTransformation;
Matrix m_RelativeTransformation;
SceneNode * m_Parent;
};
Et là, le compilateur ne râle plus ! On a donc un getter parfaitement sécurisé, qui garantit la constance de l’instance sur laquelle il est appelé, tout en autorisant le caching du résultat !
Une petite note toutefois concernant ce mot clef : attention à son utilisation ! Il existe le risque de l’utiliser pour corriger de façon fonctionnelle un problème conceptuel ! (oui, c’est bizarre comme phrase, mais bon en gros si une méthode const modifie le contenu de sa classe, c’est souvent un défaut de conception qui peut être dangereux, alors en croyant le “corriger” en utilisant mutable, on ne fait en fait que masquer le réel problème :))
Sources :
http://msdn.microsoft.com/en-us/library/4h2h0ktk(v=vs.80).aspx
http://www.highprogrammer.com/alan/rants/mutable.html