Antipattern mit konstanter Arraygröße

Die Übersetzung des Artikels wurde im Vorfeld des Kursbeginns „C ++ Developer. Professionell " .








Ich möchte Ihre Aufmerksamkeit auf das Anti-Muster lenken, auf das ich häufig im Code von Schülern des Code Review StackExchange und sogar in einer relativ großen Anzahl von Lehrmaterialien (!) Anderer Personen stoße. Sie haben eine Reihe von beispielsweise 5 Elementen; und dann, da magische Zahlen schlecht sind, führen sie eine benannte Konstante ein, um die Kardinalität von "5" darzustellen.



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




Aber die Lösung ist so lala! Im obigen Code wird die Nummer fünf wiederholt : zuerst im Wert myArraySize = 5und dann erneut, wenn Sie die Elemente tatsächlich zuweisen myArray. Der obige Code ist vom Standpunkt der Wartung aus genauso schrecklich wie:



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




- was natürlich keiner von uns jemals schreiben wird.



Wiederholter Code ist nicht gut



Beachten Sie, dass Sie in beiden obigen Codefragmenten jedes Mal, wenn Sie den Inhalt des Arrays oder den Wortlaut der Nachricht ändern, zwei Codezeilen anstelle einer aktualisieren müssen . Hier ist ein Beispiel dafür, wie ein Betreuer diesen Code möglicherweise falsch aktualisiert :



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




Der Patch oben aussieht wie es ändert den Inhalt des Arrays von 2,7,1,8,2 zu 3,1,4 , aber es ist nicht! Tatsächlich ändert es sich in 3,1,4,0,0 - mit Nullenauffüllung - weil der Betreuer vergessen hat, sich myArraySizeentsprechend anzupassen myArray.



Zuverlässiger Ansatz



Computer können verdammt gut zählen. Also lass den Computer zählen!



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




Jetzt können Sie den Inhalt des Arrays ändern , beispielsweise von 2,7,1,8,2 auf 3,1,4 , indem Sie nur eine Codezeile ändern. Sie müssen die Änderung nirgendwo duplizieren.



Darüber hinaus verwendet myArrayrealer Code normalerweise Schleifen forund / oder Algorithmen, die auf dem Iteratorbereich basieren , um ihn zu manipulieren , sodass zum Speichern der Größe des Arrays überhaupt keine benannte Variable erforderlich ist.



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

// Warning: Unused variable 'myArraySize'




Die "schlechte" Version dieses Codes wird myArraySizeimmer verwendet (in der Deklaration myArray), und daher ist es unwahrscheinlich, dass der Programmierer sieht, dass er ausgeschlossen werden kann. In der "guten" Version kann der Compiler leicht erkennen, was myArraySizenicht verwendet wird.



Wie geht das std::array?



Manchmal macht ein Programmierer einen weiteren Schritt in Richtung der Dunklen Seite und schreibt:



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




Dies sollte mindestens wie folgt umgeschrieben werden:



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




Es gibt jedoch keine einfache Möglichkeit, das manuelle Zählen in der ersten Zeile zu beseitigen. Mit CTAD C ++ 17 können Sie schreiben



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




Dies funktioniert jedoch nur, wenn Sie ein Array benötigen. intEs funktioniert nicht, wenn Sie shortbeispielsweise ein Array oder ein Array benötigen uint32_t.



C ++ 20 gibt uns std :: to_array , mit dem wir schreiben können



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




Beachten Sie, dass dies ein C-Array erstellt und dann seine Elemente in (Verschiebungskonstrukte) verschiebtstd::array . Alle unsere vorherigen Beispiele wurden myArraymit einer geschweiften Initialisierungsliste initialisiert, die die aggregierte Initialisierung auslöste und die vorhandenen Array-Elemente instanziierte.



In jedem Fall führen alle diese Optionen zu einer großen Anzahl zusätzlicher Vorlageninstanzen im Vergleich zu guten alten C-Arrays (für die keine Vorlageninstanziierung erforderlich ist). Daher bevorzuge ich T[]das neuere std::array<T, N>.



In C ++ 11 und C ++ 14 hatte std::arrayes einen ergonomischen Vorteil, sagen zu können arr.size(); Dieser Vorteil verschwand jedoch, als uns C ++ 17 zur Verfügung stelltestd::size(arr)und für Inline-Arrays. Es std::arraygibt keine ergonomischen Vorteile mehr. Verwenden Sie es, wenn Sie die Semantik der gesamten Objektvariablen möchten (übergeben Sie das gesamte Array an eine Funktion! Geben Sie ein Array von einer Funktion zurück! Weisen Sie Arrays mit = zu! Vergleichen Sie Arrays mit ==!). Ansonsten empfehle ich, die Verwendung zu vermeiden std::array.



Ebenso empfehle ich zu vermeiden std::list, es sei denn, Sie benötigen die Stabilität des Iterators, schnelles Kleben, Sortieren ohne Ersetzen von Elementen usw. Ich sage nicht, dass diese Typen in C ++ keinen Platz haben; Ich sage nur, dass sie "sehr spezifische Fähigkeiten" haben, und wenn Sie diese Fähigkeiten nicht einsetzen, zahlen Sie wahrscheinlich zu viel.




Schlussfolgerungen: Den Wagen nicht vor dem Pferd umzäunen. Tatsächlich wird der Wagen möglicherweise nicht einmal benötigt. Und wenn Sie das Zebra für die Arbeit des Pferdes verwenden müssen, sollten Sie den Wagen auch nicht vor dem Zebra umzäunen.





Weiterlesen:






All Articles