dimanche 6 juillet 2008

Ils font rire les gosses mes static*

[*] Point bonus à ceux qui pigent la vanne

Le mot-clé "static" est une cochonnerie. Ou plutôt, un outil bien spécifique utilisé à tort et à travers dans le mauvais code. En fait, l'on pourrait probablement dire que "static", c'est le SUV du C++. Non, vous n'en avez pas besoin pour aller chercher le pain. Ni même pour aller en vacances à Biarritz. En revanche, si vous écumez les chemins de terre, on peut commencer à discuter.

Ecumons donc les chemins de terre!
Tout d'abord, et afin de tout compliquer, le mot-clé "static" a plusieurs utilisations.

  • Tout d'abord, et c'est un héritage du C, une variable globale "static" n'est visible que dans son unité de compilation. Cette utilisation est tombée en désuétude avec l'apparition des namespaces, et notamment des namespaces anonymes.

  • Ensuite, et c'est encore du C, une variable statique dans une fonction ou une méthode agit comme une variable globale visible uniquement dans son scope, initialisée la première fois que la fonction est appelée, et gardant sa valeur d'un appel à l'autre.

  • Enfin, et cette fois ci c'est du vrai C++, les méthodes et attributs d'une classe peuvent être déclarés static, c'est à dire relatifs à une classe et non à une instance.

J'ai trouvé que la variable statique dans une fonction était particulièrement utile pour débugger son programme. Par exemple, je cherche à afficher pour chaque appel de la fonction quelles étaient ses paramètres:


int f(int a, int b)
{
static n = 0;
n++;
std::cout << "Appel" << n << " - " << a << ", " << b << std::endl;
...
}


Ceci affichera, par exemple:

Appel 1 - 3, 7
Appel 2 - 7, 43
...
Appel 254 - 4, 25

Voilà, mais par contre, ce n'est pas particulièrement utile en production!

Une deuxième utilisation est d'utiliser une variable statique comme cache. Par exemple, imaginons que la fonction f soit très gourmande en calcul, mais que par ailleurs elle soit uniquement dépendante de ses paramètres, et qu'elle soit souvent appelée avec les mêmes. L'on peut faire la chose suivante:


int f(int a, int b)
{
typedef std::map<std::pair<int, int>, int> CacheT;
static CacheT cache;
CacheT::const_iterator it = cache.find(std::make_pair(a, b));
if(it != cache.end())
{
return it->second;
}
else
{
int result = calcul_long(a, b);
cache.insert(std::make_pair(std::make_pair(a, b), result));
return result;
}
}


Et voilà, un beau cache ajouté à la fonction, sans rien cochonner autour! Par contre, cette technique montre assez rapidement ses limites, particulièrement lorsque le code est inclus dans un service censé tourner 24h/24. Il est en effet impossible de vider le cache, ou d'en tirer des statistiques. Même si l'on implémente un cache plus malin qui ne garde que les n dernières entrées, la taille des entrées est fixée à la compilation. Ou alors, il faut la passer systématiquement en paramètre de la fonction. Si c'est le cas, pourquoi ne pas passer le cache lui-même, ce qui sera bien plus propre. Et que l'on ne vienne pas me parler de singleton, les variables globales, c'est le mal!


En parlant de variables globales, causons un peu des méthodes et attributs statiques. Sur le sujet, je dirais que c'est rarement nécessaire, et souvent dangereux, en dehors des cas où ils sont utilisés à travers des templates. Par exemple, lors de l'implémentation d'une factory qui prend mes messages ayant des identifiants.


class LoginMessage
{
...
static int getId() {return 1};
};

class LogoutMessage
{
...
static int getId() {return 2;}
};


Quand j'enregistre mon message dans ma factory en passant le message comme un paramètre template, je peux en extraire l'identifiant.


template<typename T>
registerMessage()
{
m_messageMap.insert(T::getId(), ...); // ... est par exemple un pointeur vers une fonction qui renvoie un nouvel objet T
}


Et je peux donc enregistrer mes messages ainsi:


factory.registerMessage<LoginMessage>();
factory.registerMessage<LogoutMessage>();


Voilà, j'en ai écrit un peu plus que ce que j'aurais imaginé. Probablement la frustration de devoir maintenir du code plein d'horreurs statiques dans tous les sens. Alors, "static", évitez-le quand vous le pouvez, utilisez-le quand vous le devez!

Aucun commentaire: