Antipattern de constante de taille de tableau

La traduction de l'article a été préparée en prévision du début du cours «Développeur C ++. Professionnel " .








Je veux attirer votre attention sur l'anti-pattern, que je rencontre souvent dans le code des étudiants sur le Code Review StackExchange et même dans un assez grand nombre de matériels pédagogiques (!) D'autres personnes. Ils ont un tableau de, disons, 5 éléments; puis, puisque les nombres magiques sont mauvais, ils introduisent une constante nommée pour représenter la cardinalité de "5".



void example()
{
    constexpr int myArraySize = 5;
    int myArray[myArraySize] = {2, 7, 1, 8, 2};
    ...




Mais la solution est moyenne! Dans le code ci-dessus, le numéro cinq est répété : d'abord dans la valeur myArraySize = 5, puis à nouveau lorsque vous affectez réellement les éléments myArray. Le code ci-dessus est tout aussi terrible du point de vue de la maintenance que:



constexpr int messageLength = 45;
const char message[messageLength] =
    "Invalid input. Please enter a valid number.\n";




- qui, bien sûr, aucun de nous n'écrira jamais.



Le code répété n'est pas bon



Notez que dans les deux extraits de code ci-dessus, chaque fois que vous modifiez le contenu du tableau ou le libellé du message, vous devez mettre à jour deux lignes de code au lieu d'une. Voici un exemple de la façon dont un responsable peut mettre à jour ce code de manière incorrecte :



   constexpr int myArraySize = 5;
-   int myArray[myArraySize] = {2, 7, 1, 8, 2};
+   int myArray[myArraySize] = {3, 1, 4};




Le correctif ci-dessus semble changer le contenu du tableau de 2,7,1,8,2 à 3,1,4 , mais ce n'est pas le cas! En fait, il le change en 3,1,4,0,0 - avec un remplissage de zéros - parce que le responsable a oublié d'ajuster en myArraySizefonction de myArray.



Approche fiable



En ce qui concerne le comptage, les ordinateurs sont vraiment très bons. Alors laissez l'ordinateur compter!



int myArray[] = {2, 7, 1, 8, 2};
constexpr int myArraySize = std::size(myArray);




Vous pouvez maintenant changer le contenu du tableau, disons de 2,7,1,8,2 à 3,1,4 , en ne changeant qu'une seule ligne de code. Vous n'avez pas besoin de dupliquer la modification n'importe où.



De plus, myArrayle code réel utilise généralement des boucles foret / ou des algorithmes basés sur la plage de l'itérateur pour le manipuler , il n'a donc pas besoin du tout d'une variable nommée pour stocker la taille du tableau.



for (int elt : myArray) {
    use(elt);
}
std::sort(myArray.begin(), myArray.end());
std::ranges::sort(myArray);

// Warning: Unused variable 'myArraySize'




La "mauvaise" version de ce code est myArraySizetoujours utilisée (dans la déclaration myArray), et donc le programmeur ne verra probablement pas qu'elle peut être exclue. Dans la «bonne» version, il est facile pour le compilateur de détecter ce qui n'est myArraySizepas utilisé.



Comment faire cela std::array?



Parfois, un programmeur fait un autre pas vers le côté obscur et écrit:



constexpr int myArraySize = 5;
std::array<int, myArraySize> myArray = {2, 7, 1, 8, 2};




Cela devrait être au moins réécrit:



std::array<int, 5> myArray = {2, 7, 1, 8, 2};
constexpr int myArraySize = myArray.size();  //  std::size(myArray)




Cependant, il n'y a pas de moyen facile de se débarrasser du comptage manuel sur la première ligne. CTAD C ++ 17 vous permet d'écrire



std::array myArray = {2, 7, 1, 8, 2};




mais cela ne fonctionne que si vous avez besoin d'un tableau int- cela ne fonctionnera pas si vous avez besoin d'un tableau short, par exemple, ou d'un tableau uint32_t.



C ++ 20 nous donne std :: to_array , ce qui nous permet d'écrire



auto myArray = std::to_array<int>({2, 7, 1, 8, 2});
constexpr int myArraySize = myArray.size();




Notez que cela crée un tableau C puis déplace (déplacez-les) ses éléments dans std::array. Tous nos exemples précédents ont été initialisés myArrayavec une liste d' initialiseurs à accolades qui a déclenché l'initialisation de l'agrégat et instancié les éléments du tableau en place.



Dans tous les cas, toutes ces options se traduisent par un grand nombre d'instances de modèles supplémentaires par rapport aux bons vieux tableaux C (qui ne nécessitent pas d'instanciation de modèle). Par conséquent, je préfère fortement T[]le plus récent std::array<T, N>.



En C ++ 11 et C ++ 14, std::arrayil y avait un avantage ergonomique à pouvoir dire arr.size(); mais cet avantage s'est évaporé lorsque C ++ 17 nous a fournistd::size(arr)et pour les tableaux en ligne. Il std::arrayn'y a plus d'avantages ergonomiques. Utilisez-le si vous voulez toute sa sémantique de variable objet (passez le tableau entier à une fonction! Renvoyez un tableau à partir d'une fonction! Assignez des tableaux avec =! Comparez les tableaux avec ==!), Mais sinon, je recommande d'éviter d'utiliser std::array.



De même, je recommande d'éviter std::list, à moins que vous ne vouliez la stabilité de son itérateur, de coller rapidement, de trier sans remplacer d'éléments, etc. Je ne dis pas qu'il n'y a pas de place pour ces types en C ++; Je dis simplement qu'ils ont un «ensemble de compétences très spécifiques», et si vous n'utilisez pas ces compétences, vous paierez probablement trop cher.




Conclusions: ne clôturez pas la charrette devant le cheval. En fait, le chariot n'est peut-être même pas nécessaire. Et si vous devez utiliser le zèbre pour faire le travail du cheval, vous ne devez pas non plus clôturer la charrette devant le zèbre.





Lire la suite:






All Articles