Array size constant antipattern
I want to draw your attention to the anti-pattern, which I often come across in the code of students on the Code Review StackExchange and even in a fairly large number of educational materials (!) Of other people. They have an array of, say, 5 elements; and then, since magic numbers are bad, they introduce a named constant to represent the cardinality of “5”.
void example()
{
constexpr int myArraySize = 5;
int myArray[myArraySize] = {2, 7, 1, 8, 2};
...
But the solution is so-so! In the above code, the number is five repeats: first in value myArraySize = 5
and then again when you actually assign the elements myArray
… The above code is just as terrible from a maintenance standpoint as:
constexpr int messageLength = 45;
const char message[messageLength] =
"Invalid input. Please enter a valid number.n";
– which, of course, none of us will ever write.
Repeated code is not good
Note that in both of the above code snippets, every time you change the contents of the array or the wording of the message, you must update two lines of code instead of one. Here’s an example of how a maintainer can update this code. wrong:
constexpr int myArraySize = 5;
- int myArray[myArraySize] = {2, 7, 1, 8, 2};
+ int myArray[myArraySize] = {3, 1, 4};
Patch above looks like as if it modifies the contents of the array with 2,7,1,8,2 on the 3,1,4but it is not! In fact, he changes it to 3,1,4,0,0 – padded with zeros – because the maintainer forgot to adjust myArraySize
in accordance with myArray
…
Reliable approach
As for counting, computers are pretty damn good at it. So let the computer count!
int myArray[] = {2, 7, 1, 8, 2};
constexpr int myArraySize = std::size(myArray);
Now you can change the contents of the array, say with 2,7,1,8,2 on the 3,1,4by changing just one line of code. You don’t need to duplicate the change anywhere.
Even more, to manipulate myArray
real code usually uses loops for
and / or algorithms based on the range of the iterator, so it won’t need a named variable at all to store the size of the array.
for (int elt : myArray) {
use(elt);
}
std::sort(myArray.begin(), myArray.end());
std::ranges::sort(myArray);
// Warning: Unused variable 'myArraySize'
In the “bad” version of this code myArraySize
always used (in the declaration myArray
), and therefore the programmer is unlikely to see that it can be excluded. In the “good” version, it is easy for the compiler to find that myArraySize
not used.
How to do it with std::array
?
Sometimes a programmer takes another step towards the Dark Side and writes:
constexpr int myArraySize = 5;
std::array<int, myArraySize> myArray = {2, 7, 1, 8, 2};
This should be rewritten as at least:
std::array<int, 5> myArray = {2, 7, 1, 8, 2};
constexpr int myArraySize = myArray.size(); // или std::size(myArray)
However, there is no easy way to get rid of the manual counting on the first line. CTAD C ++ 17 lets you write
std::array myArray = {2, 7, 1, 8, 2};
but this only works if you need an array int
– it won’t work if you need an array short
for example or an array uint32_t
…
C ++ 20 gives us std :: to_arraywhich allows us to write
auto myArray = std::to_array<int>({2, 7, 1, 8, 2});
constexpr int myArraySize = myArray.size();
Note that this creates a C array and then move (move-constructs) its elements in std::array
… All of our previous examples have initialized myArray
using a curly-braced initializer list that triggered aggregate initialization and instantiated the array elements directly in place.
In any case, all of these options result in a large number of additional template instances compared to good old C arrays (which don’t require template instantiation). Therefore, I strongly prefer T[]
newer
…std::array<
T, N>
In C ++ 11 and C ++ 14, std::array
had the ergonomic advantage of being able to say arr.size()
; but that advantage evaporated when C ++ 17 provided us with std::size(arr)
and for inline arrays. Have std::array
there are no more ergonomic advantages. Use it if you need its whole object variable semantics (pass the entire array to a function! Return an array from a function! Assign arrays with =! Compare arrays with ==!), But otherwise I recommend avoiding using std::array
…
Likewise, I recommend avoiding
std::list
if you don’t need the stability of its iterator, fast gluing, sorting without replacing elements, etc. I’m not saying that there is no place for these types in C ++; I’m just saying that they have a “very specific set of skills,” and if you don’t use those skills, you are likely overpaying.
Conclusions: do not fence the cart in front of the horse. In fact, the cart may not even be needed. And if you need to use the zebra to do the horse’s work, you also shouldn’t fence the cart in front of the zebra.
Read more:
- Examples of C ++ code before and after Ranges