Quelques erreurs « classiques »

Quelques erreurs « classiques »

par Jean-Cédric Chappelier,
Nombre de réponses : 0

A force de répondre à toutes sortes de questions, j'ai vu à plusieurs reprises des choses qui m'ont « surprises » (car elles sont en contradiction avec les principes présentés en cours) et que je voudrais partager ici (afin d'améliorer votre apprentissage) :

  • pourquoi rajouter un destructeur ?
    Il y a toujours le destructeur fourni par défaut et celui-ci suffit dans l'immense majorité des cas.
    De plus, je rappelle « la règle des 3 » (voire « la règles des 5 », niveau avancé (move)) : si l'on touche à l'un des 3 que sont le destructeur, le constructeur de copie et l'opérateur d'affectation, alors on doit certainement toucher aux 2 autres ou, en tout cas, vraiment se poser la question.

    Du coup, question subsidiaire : quand est-ce que le destructeur fourni par défaut ne suffit pas ?
    Quand on a des ressources à gérer ou alors si le destructeur doit être virtuel.
    Pour ce dernier point : revoir le début de la vidéo « d'étude de cas » de la semaine 7 (« polymorphisme 2») de 2:00 à 6:16.

  • pourquoi delete le constructeur par défaut ?
    Je rappelle que du moment que l'on fournit un constructeur, le constructeur par défaut par défaut n'est plus fourni. Il est donc inutile de le delete.

  • mauvaise utilisation des pointeurs : revoir les « 3 cas d'utilisation » au début du cours sur les pointeurs : chaque fois que vous utilisez un pointeur, demandez vous (d'abord si c'est vraiment nécessaire, puis) pourquoi : pour quel « cas d'utilisation » (référence, généricité ou allocation dynamique) ? Et donc deux rappels fondamentaux :
    (1) utilisez des références chaque fois que vous le pouvez, des pointeurs quand vous le devez ;
    (2) les « smart pointers » (et donc en particulier les `unique_ptr`) ne sont à utiliser QUE (et uniquement que !) dans le « cas n°3 » , l'allocation dynamique ; ils n'ont RIEN A FAIRE dans les 2 autres cas ;

  • précision des représentations en `double`: cf cours I.4 d'ICC : les `double` sont des représentations finies « intrinsèquement » entachées d'erreur relative (de l'ordre de `1e-16`) et cette erreur s'accumule au fur et à mesure des calculs. Il est donc illusoire (voire totalement faux) de vouloir reproduire les mêmes valeurs à une précision inférieure à cette erreur (donc par exemple inférieure à `1e-14` si l'on fait une centaine d'opérations)

  • mauvaise conception 1 : abus de « méthodes get » (et de « méthodes set ») : je rappelle que les méthodes mises dans l'interface (la partie `public`) ne doivent être que QUELQUES méthodes BIEN CHOISIES : abuser de `get` sur les attributs est une très mauvaise idée (« fuite d'encapsulation »).
    Un schéma en particulier est à éviter : pour une instance `a` d'une classe `A` ayant un attribut `x` d'une classe B avant une méthode `f()` : mettre un `get_x()` dans `A` juste pour appeler `f()` :
    a.get_x().f()
    ou
    B bidule(a.get_x());
    bidule.f();
    est une très mauvaise conception !!
    Pourquoi ne pas simplement « déléguer » l'appel de `f()` à `A` ?  Par exemple en ajoutant une méthode `f()` (ou `f_de_B()` ou n'importe quel nom pertinent) à la classe A, laquelle fait simplement un `return x.f();` ? (Et donc on appelle simplement `a.f()`) ?
    Cette remarque est d'autant plus pertinent quand `x` est un `vector`! Au lieu de « get » ce `vector` et itérer à l'extérieur, on peut souvent « déléguer » le travail à `A` et faire l'itération à l'intérieur de cette classe...

  • mauvaise conception 2: collections « pensées à l'envers » : admettons que nous ayons des classes polymorphiques A et B (ils y a donc typiquement encore d'autres sous-classe) telles que B hérite de A . Une erreur de conception que je vois parfois est de mettre une collection de (références ou pointeurs vers des) A alors qu'une collection de (références ou pointeurs vers des) B suffit !
    Tout B est un A, mais tout A n'est pas un B : si rechercher les B dans la collection de A devient un problème, c'est que la conception à certainement été « pensée à l'envers ».
    Si vraiment (mais c'est rarement le cas) on a besoin d'une collection de A et d'une collection de B (pour les retrouver), alors pourquoi ne pas simplement mettre les deux ? Les pointeurs ou références sont justement là pour ça ! (= il n'y aura pas de copie d'objets) : on parcourt alors l'une ou l'autre de ces collections suivant le besoin.
Voilà pour ce qui me revient à l'esprit.
Je compléterai si nécessaire ce thread si d'autres cas me reviennent.