Antipatrón constante de tamaño de matriz

La traducción del artículo se preparó con anterioridad al inicio del curso “C ++ Developer. Profesional " .








Quiero llamar su atención sobre el anti-patrón, que a menudo me encuentro en el código de los estudiantes en Code Review StackExchange e incluso en una cantidad bastante grande de materiales educativos (!) De otras personas. Tienen una serie de, digamos, 5 elementos; y luego, dado que los números mágicos son malos, introducen una constante nombrada para representar la cardinalidad de "5".



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




¡Pero la solución es regular! En el código anterior, el número cinco se repite : primero en el valor myArraySize = 5y luego nuevamente cuando realmente asigna los elementos myArray. El código anterior es tan terrible desde el punto de vista del mantenimiento como:



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




- que, por supuesto, ninguno de nosotros escribirá jamás.



El código repetido no es bueno



Tenga en cuenta que en los dos fragmentos de código anteriores, cada vez que cambie el contenido de la matriz o la redacción del mensaje, debe actualizar dos líneas de código en lugar de una. A continuación, se muestra un ejemplo de cómo un mantenedor podría actualizar este código incorrectamente :



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




El parche anterior parece que cambia el contenido de la matriz de 2,7,1,8,2 a 3,1,4 , ¡pero no lo es! De hecho, lo cambia a 3,1,4,0,0 - con relleno de ceros - porque el encargado de mantenimiento se olvidó de ajustar de myArraySizeacuerdo con myArray.



Enfoque confiable



Las computadoras son muy buenas contando. ¡Deja que la computadora cuente!



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




Ahora puede cambiar el contenido de la matriz, digamos de 2,7,1,8,2 a 3,1,4 , cambiando solo una línea de código. No es necesario duplicar el cambio en ninguna parte.



Aún más, myArrayel código real generalmente usa bucles fory / o algoritmos basados ​​en el rango del iterador para manipularlo , por lo que no necesita una variable con nombre para almacenar el tamaño de la matriz.



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

// Warning: Unused variable 'myArraySize'




La versión "mala" de este código myArraySizesiempre se usa (en la declaración myArray) y, por lo tanto, es poco probable que el programador vea que se puede excluir. En la versión "buena", es fácil para el compilador detectar lo que myArraySizeno se está utilizando.



¿Cómo hacer esto con std::array?



A veces, un programador da otro paso hacia el lado oscuro y escribe:



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




Esto debería reescribirse como al menos:



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




Sin embargo, no existe una manera fácil de deshacerse del recuento manual en la primera línea. CTAD C ++ 17 te permite escribir



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




pero esto solo funciona si necesita una matriz int; no funcionará si necesita una matriz short, por ejemplo, o una matriz uint32_t.



C ++ 20 nos da std :: to_array , que nos permite escribir



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




Tenga en cuenta que esto crea una matriz C y luego mueve (mueve-construye) sus elementos a std::array. Todos nuestros ejemplos anteriores se inicializaron myArraycon una lista de inicializadores entre llaves que desencadenó la inicialización agregada y creó una instancia de los elementos de la matriz en su lugar.



En cualquier caso, todas estas opciones dan como resultado una gran cantidad de instancias de plantilla adicionales en comparación con las antiguas matrices C (que no requieren instanciación de plantilla). Por lo tanto, prefiero fuertemente T[]el más nuevo std::array<T, N>.



En C ++ 11 y C ++ 14, std::arrayhabía una ventaja ergonómica en poder decir arr.size(); pero esa ventaja se evaporó cuando C ++ 17 nos proporcionóstd::size(arr)y para matrices en línea. No std::arrayhay más ventajas ergonómicas. Úselo si necesita toda la semántica de la variable de objeto (¡pase la matriz completa a una función! ¡Devuelva una matriz de una función! ¡Asigne matrices con =! Compare matrices con ==!), Pero de lo contrario, recomiendo evitar usar std::array.



Asimismo, recomiendo evitarlo std::lista menos que desee la estabilidad de su iterador, encolado rápido, ordenando sin reemplazar elementos, etc. No estoy diciendo que no haya lugar para estos tipos en C ++; Solo digo que tienen un "conjunto de habilidades muy específicas", y si no las usa, probablemente esté pagando de más.




Conclusiones: no valla el carro delante del caballo. De hecho, es posible que el carro ni siquiera sea necesario. Y si necesitas usar la cebra para hacer el trabajo del caballo, tampoco debes colocar el carro frente a la cebra.





Lee mas:






All Articles