Coder efficacement

Bonnes pratiques et erreurs à éviter (en C++)

4. Éviter les effets de bord

On parle d'effet de bord lorsqu'une fonction modifie une variable qui existe, sous une forme ou une autre, en dehors de la fonction dans laquelle elle est utilisée.

Il y a, bien sûr, des circonstances dans lesquelles on n'a pas vraiment le choix et où l'effet de bord fait partie intégrante du comportement que l'on attend de la part de la fonction. Cependant, à l'heure où l'on parle de plus en plus de multithreading et de mémoire partagée (ce n'est pas un hasard si C⁠+⁠+11 fournit désormais la classe std::thread) les fonctions qui occasionnent un effet de bord, qu'il soit volontaire ou non, s’avéreront particulièrement contraignantes à gérer.

Il s'agira en effet de s'assurer en toutes circonstances que l'effet de bord occasionné par la fonction en question n'ait pas d'impact sur l'exécution des fonctions qui utilisent peut-être la variable modifiée par cet effet de bord par ailleurs.

Obtenir une telle garantie peut s'avérer particulièrement difficile.

Prenons l'exemple d'une structure très simple, qui permet de représenter une coordonnée sous la forme de

  1 struct Coordinate{
  2     size_t x;
        size_t y;
  4     size_t z;
    };

Une fonction provoquant un effet de bord pourrait ressembler à

  1 void modify(Coordinate & coord,
  2             size_t newX,
                size_t newY,
  4             size_t newZ){
        coord.x = newX;
  6     coord.y = newY;
        coord.z = newZ;
  8 }

Elle serait alors utilisée sous une forme qui ressemblerait à s'y méprendre à quelque chose comme

  1 int main(){
  2     Coordinate coordiate;
        modifyCoordinate(coordinate, 5,300,410);
  4     /* ... */
        return 0;
  6 }

Une telle utilisation ne posera pas de problème et nous pourrons observer exactement le comportement auquel on est en droit de s'attendre : la coordonnée est bel et bien modifiée dans la fonction appelante après avoir appelé notre fonction modifiyCoordinate.

Par contre, dans un contexte multithreadé, ce comportement risque de poser problème car il est tout à fait possible que deux threads utilisent la même coordonnée en même temps. Pour peu qu'un des threads essaye de récupérer les valeurs qu'elle contient pendant que l'autre fait appel à modifyCoordinate, il est tout à fait possible qu'il récupère une partie des valeurs alors qu'elles n'ont pas encore été modifiées et l'autre partie des valeurs alors qu'elles ont déjà été modifiées. Les résultats obtenus dans cette situation seront -- vous vous en doutez -- particulièrement aberrants. Et le problème serait encore pire si les deux threads essayaient de modifier cette donnée en même temps !

Ce phénomène porte le nom de data race en anglais. Bien sûr, il existe différentes solutions pour éviter ce genre de situations, mais il n'entre pas dans le cadre de cet ouvrage de commencer à les expliquer. Si vous voulez en savoir davantage à ce point de vue, faites une recherche sur les termes "data race" ou "accès concurrent" via votre moteur de recherche favori.

Notez que, sans aller jusqu'au concept du multithreading, les effets de bords sont également susceptibles de poser des problèmes de réentrance ou lorsque l'on manipule des variables globales.

En outre, cela rend le débogage bien plus compliqué lorsqu'il s'agit de savoir où une variable soumise à un effet de bord est modifiée.

Bien sûr, il semble pour le moins impossible d'éviter en toutes circonstances les effets de bord (volontaires !) car ces effets de bord sont parfois la "moins mauvaise solution" à envisager, pour des raisons de performance, par exemple. Il serait donc tout à fait utopique de vous dire de ne jamais créer de fonction qui occasionne un effet de bord, c'est un conseil que vous seriez bien incapable de respecter.

Par contre, si je vous conseille de provoquer le moins d'effets de bord possible, uniquement lorsque c'est absolument nécessaire et justifié et dans des circonstances clairement établies, vous aurez peut-être une chance de pouvoir respecter ce conseil.

Au final, je dirais surtout : Si vous êtes en mesure d'éviter un effet de bord, préférez la solution qui l'évitera.