Array übergeben zu Konstruktor



  • @Belli Das stimmt - man kann beim Initialisieren eines vectors die Größe gleich mit angeben und man könnte so - je nach Fall - durchaus die resize-Operation verhindern.

    Dennoch ist der Sinn eines std::vector ja genau das: ich möchte schnelle Zugriffsgeschwindigkeiten und möchte nicht durch die fixe Größe meiner Datenstruktur eingeschränkt werden. Daher nehme ich zur Not gerne eine resize Operation in Kauf.
    Es spricht auch prinzipiell nichts dagegen deinen Code ausschließlich mit std::vector zu würzen 🙂

    Dennoch hat std::array ja seine Existenzberechtigung und STL kommt ja nicht ohne Grund mit dieser Datenstruktur. Ein Beispiel, was mir spontan einfallen würde, wäre eine API, welche Kalender-Funktionalitäten bereit stellt. Da wäre es durchaus sauberer folgendes zu tun:

    using weekDays = std::array<std::string, 7>;
    

    Mit folgendem API Endpunkt:

    void printWeekDays(const weekDays& _weekDays)
    {
      // code usw.
    }
    

    Warum? Weil ich damit eigentlich ein potentielles CVE für diese Pseudo-Library bereits verhindert habe, da weekDays genau definiert ist - gerade, was die Größe betrifft. Ich kann nämlich dann nicht das hier tun:

    std::vector<std::string> weekDays = {"Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag","Bielefeld","Tag des jüngsten Gerichts"};
    
    printWeekDays(weekDays); // würde auch "Bielefeld" usw. ausgeben - viel mehr als es Wochentage gibt
    

    Klar - das Beispiel hinkt ein bisschen, da das ja nicht das Problem löst welche Tage in der Datenstruktur enthalten sind und ich könnte jetzt auch argumentieren, dass ich std::vector ja trotzdem nutzen kann, wenn ich in der Implementierung des Endpunktes einfach die Größe abfrage ect. - aber warum sollte ich das tun, wenn ich über die Datenstruktur selbst bereits Grenzen setzen kann?
    Und übrigens: Du siehst in meinem Beispiel, dass ich mittels using gar nicht mal so viel tippen müsste 🙂



  • Dein weekdays-Beispiel ist kein gutes Beispiel, das gegen vector spricht, weil man aus diesem vector wohl nur lesen muss:

    const vector<string> weekdays {"Mo", "Di", "Mi" /*usw.*/};
    

    Deshalb kann man ihn einfach const definieren.



  • @Belli Was hat denn const damit zu tun, wenn ich nicht möchte, dass mehr als 7 Wochentage übergeben werden?
    Ob ich nun:

    const std::vector<std::string> weekDays = {"Montag", "Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag"};
    

    an einen Endpunkt mit folgender Signatur übergebe:

    void printWeekDays(const std::vector<std::string>& _weekDays);
    

    oder:

    const std::vector<std::string> weekDays = {"Montag", "Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag","blabla","nochmal blabla"};
    

    macht von der Signatur her keinen Unterschied - ob mit oder ohne const.
    Mit dem eigenen Typen:

    using weekDays = std::array<std::string, 7>;
    

    kann ich das zumindest, was die Größe meiner Datenstruktur betrifft, kontrollieren.



  • @john-0 sagte in Array übergeben zu Konstruktor:

    @DocShoe sagte in Array übergeben zu Konstruktor:

    Ich rate dazu, statt eines rohen Arrays std::vector zu benutzen,

    Diese sollte man nicht in Konstruktoren nutzen, da bieten sich eine std::initializer_list an.

    @SeppJ sagte in Array übergeben zu Konstruktor:

    @john-0 sagte in Array übergeben zu Konstruktor:

    @DocShoe sagte in Array übergeben zu Konstruktor:

    Ich rate dazu, statt eines rohen Arrays std::vector zu benutzen,

    Diese sollte man nicht in Konstruktoren nutzen, da bieten sich eine std::initializer_list an.

    Hier im Konstruktor initializer_list, aber anderswo auch keinen Vector (oder andere konkrete Container, außer string) übergeben. Iteratoren (oder meinetwegen auch ein Templateparameter aus dem der Code sich dann selber eine Iteration macht) sind immer besser geeignet.

    Mal blöd gefragt, was spricht dagegen Container im Konstruktor zu übergeben?
    Den Vorteil von Iteratoren für generichen Code sehe ich ein, aber auch hier habe ich schon häufig Funktionen gesehen, die einen stl Algorithmus nochmal auf einem ganzen Container kapseln, so dass man nicht immer std::begin() und std::end() schreiben muss.

    Aber, wenn ich wirklich Daten einer unbekannten Anzahl weiter geben will / muss, was spricht dagegen die in einem Vektor weiter zu geben?
    Bei std::initializer_list muss die Länge der Liste ja auch zur Compilezeit feststehen, ist also, wenn die Länge erst zur Laufzeit feststeht, nicht geeignet.



  • Bin da ganz bei @Schlangenmensch.

    Und vor allem: dies ist eine Anfängerfrage. Erstmal überhaupt vector einbauen, dann weitersehen.

    Bei Iteratoren muss man erstmal eine Template-Funktion schreiben (ok, std::vector<int>::iterator ginge theoretsich - aber damit wäre ja niemandem geholfen!) und initializer_list erlaubt nicht, dass man die Daten aus einem anderen vector übernehmen kann (denn dann müsste man ja erstmal vector zu initializer_list konvertieren können).

    Die Übergabe eines Iteratorpaares ist zwar generischer und sollte auch in Bibliotekscode so stattfinden, aber ob sich das immer lohnt, ist eine andere Frage.


  • Mod

    Ihr beide klingt so, als wäre generischer Code für euch kein riesengroßer Vorteil, der jedwede andere Überlegung hinfällig macht.

    Habt Spaß, 100x den gleichen Code zu schreiben.

    Und damit meine ich nicht, dass ihr buchstäblich 100x den gleichen Code schreibt, sondern dass ihr einfach niemals irgendwelchen Code wiederverwendet, obwohl das eigentlich möglich gewesen wäre. Man denkt sich zwar immer "Ich habe von generischer Software gehört, aber ich werde niemals einen zweiten Karumbulflator schreiben oder benutzen, wieso sollte ich das generisch machen?", aber was man nicht sieht, ist, dass der Karumbulflator eigentlich aus 17 verschiedenen Teilen besteht, die man hätte wiederverwendbar machen können. Und wenn man dann einen Fazikumbator schreibt, hätte man 15 davon wiederverwenden können. Wahrscheinlich merkt man noch nicht einmal, dass da eigentlich Wiederbenutzbarkeitspotential war, weil man es nicht gewöhnt ist, wiederbenutzbar zu programmieren, und das gar nicht wahr nimmt. Den Karumbulflator hat man nämlich gar nicht aus 17 Komponenten gebaut, sondern aus 3 Riesenkomponenten, die alle viel zu viel machen. Und den Fazikumbator ebenso aus 4 anderen Superkomponenten, die nichts mit den 3 Karumbulflatorkomponenten zu tun haben. Und dann klopft man sich auf die Schulter, dass man nicht auf diesen weltfremden Informatikerquatsch aus deren Elfenbeiturm angewiesen ist, den man ja in der Praxis nie braucht.



  • @PadMad sagte in Array übergeben zu Konstruktor:

    @Belli Was hat denn const damit zu tun, wenn ich nicht möchte, dass mehr als 7 Wochentage übergeben werden?
    ...
    Mit dem eigenen Typen:

    using weekDays = std::array<std::string, 7>;
    

    kann ich das zumindest, was die Größe meiner Datenstruktur betrifft, kontrollieren.

    Es ging mir darum, dass ich nicht im Verlaufe des Programms versehentlich noch ein Element hinzufüge.
    Wenn ich zu blöd bin, das richtig zu initialisieren, dann bin wahrscheinlich auch zu blöd, für meinen weekdays - std::array - Typen die richtige Größe zu vereinbaren.



  • @SeppJ

    Die Aufgabe ist ja schon ziemlich konkret und übersichtlich. Wieviele unterschiedliche Projekte hast du denn, in der du Filmmetadaten pflegst und im Vorfeld nicht weißt, wie die Daten organisiert sind? std::vector deckt sicher >95% aller Anwendungsfälle ab, da sehe ich keinen Grund, einen Vektor nicht als Übergabeparameter zu benutzen. Und wenn´s wirklich hart auf hart kommt kann man immer noch einen temporären vector aus einem iterator-Paar bauen, um den zu übergeben.
    Generischer Code ok, aber irgendwann wird´s konkret, und bevor da was boost-ähnliches herauskommt, das völlig overengineered ist, weil Sonderfälle wie 13-bit Stringtypen unterstützt werden sollen, dann wird´s unbenutzbar, weil zu kompliziert.
    Dein Beispiel ist das eine Ende der Skala, meins das Andere. Was Vernünftiges liegt irgendwo dazwischen.


  • Mod

    @DocShoe sagte in Array übergeben zu Konstruktor:

    @SeppJ

    Die Aufgabe ist ja schon ziemlich konkret und übersichtlich. Wieviele unterschiedliche Projekte hast du denn, in der du Filmmetadaten pflegst […] Generischer Code ok, aber irgendwann wird´s konkret, und bevor da was boost-ähnliches herauskommt, das völlig overengineered ist,

    @SeppJ sagte in Array übergeben zu Konstruktor:

    Man denkt sich zwar immer "Ich habe von generischer Software gehört, aber ich werde niemals einen zweiten Karumbulflator schreiben oder benutzen, wieso sollte ich das generisch machen?" […] Wahrscheinlich merkt man noch nicht einmal, dass da eigentlich Wiederbenutzbarkeitspotential war, weil man es nicht gewöhnt ist, wiederbenutzbar zu programmieren […] Und dann klopft man sich auf die Schulter, dass man nicht auf diesen weltfremden Informatikerquatsch aus deren Elfenbeiturm angewiesen ist, den man ja in der Praxis nie braucht.

    Fällt dir was auf?



  • @Belli Was meinst Du damit? Wenn ich einer Datenstruktur ein Element hinzufüge, das aber eigentlich nicht soll / darf, weil ich nicht möchte, dass meine Datenstruktur weiter wächst, wenn eine bestimmte Größe erreicht ist, muss ich das so oder so im Code abfangen.
    Ich persönlich finde das mittels std::array einfach sauberer, wenn die Größe fix sein soll.
    Wie ich bereits sagte spricht aber auch absolut nichts dagegen std::vector zu nutzen und bei Bedarf die safety guards woanders einzubauen, wenn die Größe nicht bekannt ist.

    Weil letzten Endes hat @Johnny01 gefragt wie man ein "array" an einen Konstruktor übergeben kann. Und da ist es völlig valide zu fragen, ob die Größe zur Laufzeit bekannt bzw. fix ist. Gerade mit dem Hintergrund, dass das eine vermeintliche Anfängerfrage ist, sollte man verschiedene Möglichkeiten aufzeigen. std::vector macht hier aber vermutlich absolut Sinn.

    Ich persönlich finde es nämlich schwierig andere Datenstrukturen von vorn herein auszuschließen und std::vector als Generallösung anzubieten (nicht falsch verstehen - das ist kein finger-pointing), denn letztenendes sollten Datenstrukturen auf Grundlage der Anforderungen gewählt werden.

    @SeppJ Ich habe leider nicht ganz verstanden, was Dein letzter Text hier mit dem Thema zu tun hat und das kann vielleicht an mir liegen, aber: Generischen Code schreibt man auf Datentyp-Ebene. Nicht auf Datenstruktur-Ebene. Letzteres würde den Code ineffizient machen, da Datenstrukturen einen erheblichen Anteil an der Performance deiner Anwendung haben.



  • @SeppJ

    Klar fällt mir was auf. Sind deine Datenstrukturen alle als Templates definiert, wo du den Containertyp als Template Parameter festlegst? Bei irgendeiner bekloppten Lotterie könnten ja Duplikate gezogen werden, deswegen benutzt man kein set, sondern einen vector für die gezogenen Zahlen?
    Ja, ich verstehe dein Argument, aber in meinen Augen ist das hier völlig überzogen.



  • @PadMad
    Generischen Code schreibt man gegen Interfaces, nicht gegen konkrete Datentypen. Ein gutes Beispiel sind da die STL-Algorithmen, die meistens Iteratoren erwarten und nur Anforderungen an den Iterator-Typen und den benutzten Datentypen stellen. Als Beispiel mal die std::sort Funktion mit zwei Parametern: Sie erwartet zwei Random-Access-Iteratoren und dass der Datentyp, den die Iteratoren referenzieren, per < vergleichbar sein müssen. Woher die Iteratoren kommen (vector, list, deque, sonstwas) ist der Funktion egal. Wie der < Operator umgesetzt wird ist fast egal, Hauptsache er existiert.



  • @DocShoe Das stimmt - die Interface Ebene ist für mich Teil der Datentyp-Ebene - aber wäre das dann nicht tatsächlich eine Performance-Einbuße? Gerade bei std::sort? Aus dem Bauch heraus würde ich vermuten, dass unterschiedliche Datenstrukturen aus STL unterschiedlich performant sind, wenn es um's Sortieren geht.

    Das kann ja auch valide sein, wenn in diesem Moment die Generik wichtiger ist als die Performance - aber ich kann mir gerade nicht vorstellen, dass Generik dann nicht auch mit Kosten kommt.

    (btw. das sind tatsächlich nur Gedanken - wenn ich hierbei etwas lerne, würde ich mich tatsächlich freuen)



  • @PadMad sagte in Array übergeben zu Konstruktor:

    @Belli Was meinst Du damit? Wenn ich einer Datenstruktur ein Element hinzufüge, das aber eigentlich nicht soll / darf, weil ich nicht möchte, dass meine Datenstruktur weiter wächst, wenn eine bestimmte Größe erreicht ist, muss ich das so oder so im Code abfangen.

    Ich sehe keinen Vorteil von

    array<string, 7> weekdays = {"Mo", "Di", /*usw*/};
    

    gegenüber

    const vector<string> weekdays = {"Mo", "Di", /*usw*/};
    

    Das Array sollte übrigens vermutlich auch const sein?!

    @PadMad sagte in Array übergeben zu Konstruktor:

    Weil letzten Endes hat @Johnny01 gefragt wie man ein "array" an einen Konstruktor übergeben kann.

    Ja, das stimmt, aber er hat nicht ein std::array gemeint, sondern ein c-array.



  • Ja, das stimmt, aber er hat nicht ein std::array gemeint, sondern ein c-array.

    Korrekt. Aber was möchtest Du mir jetzt damit sagen? Dass, wenn Jemand von einem c-array redet, man durchaus den Vorschlag machen darf, dass std::vector eine gute Alternative ist, std::array aber nicht? std::vector ist jetzt erstmal auch kein c-array...

    Das Array sollte übrigens vermutlich auch const sein?!

    Natürlich - ist es ja auch - spätestens bei der Übergabe in meinem Beispiel - zudem ist const in meinem Beispiel ja völlig egal, weil ich auf die Größe der Datenstruktur angespielt habe.
    Und ich persönlich sehe hier optisch durchaus einen Unterschied, was die Anzahl der Elemente betrifft... vielleicht habe ich mich aber auch verzählt...

    std::array<std::string, 7> _weekDays = {"Mo","Di","Mi","Do","Fr","Sa","So"};
    std::vector<std::string> _weekDaysAndMore = {"Mo","Di","Mi","Do","Fr","Sa","So","1","42","0xfff","*g*"};
    

    Wenn man dieses Beispiel stellvertretend für Daten nimmt, die von Jemandem kommen, der eine API nutzen möchte mit diesem besagten Endpunkt - wo die Anzahl der Elemente fix sein soll, in diesem Fall 7 - warum sollte ich std::vector nutzen?
    Ob const oder nicht - völlig egal... weil, ob ich meine falschen Eingaben nun const mache oder nicht, macht ja die Eingaben nicht weniger falsch...

    Deswegen sollte meiner Meinung nach der besagte API Endpunkt std::vector gar nicht erst akzeptieren.


  • Mod

    @DocShoe sagte in Array übergeben zu Konstruktor:

    @SeppJ

    Klar fällt mir was auf. Sind deine Datenstrukturen alle als Templates definiert, wo du den Containertyp als Template Parameter festlegst? Bei irgendeiner bekloppten Lotterie könnten ja Duplikate gezogen werden, deswegen benutzt man kein set, sondern einen vector für die gezogenen Zahlen?

    ??? Warum sollten die internen Strukturen Templates sein?

    Ja, ich verstehe dein Argument, aber in meinen Augen ist das hier völlig überzogen.

    Da du offensichtlich genau so ein Fall wie in meinem Beispiel bist, ist das wohl um so mehr ein Argument dafür, das auch am allerkleinsten Popelbeispiel konsequent durchzuziehen. Wenn man's nie richtig gelernt hat, ist ja klar, dass es nie wie versprochen funktioniert und man das als overengineerten, unnötigen Aufwand ohne Mehrwert abtut. Es ist schließlich overengineerter, unnötiger Aufwand ohne Mehrwert, wenn man nur die Bewegungen nachäfft.

    Ist genauso wie bei objektorientierter Programmierung, wo das eigentlich eine ganz andere Denkweise ist, aber viele Leute schreiben vor schlechten Code ein class davor und denken dann "Jetzt ist es objektorientiert!". Heimlich denken sie sich, was der ganze Informatikerquatsch eigentlich soll, denn der Code ist immer noch genauso schlecht wie vorher. Und klopfen sich dann auch selber auf die Schulter wenn sie erklären, dass nicht alles eine Klasse sein braucht und sie über solchen weltfremden Paradigmen stehen. (Dabei merken sie nicht einmal, dass es nicht um Klassen sonder um eine Denkweise geht).



  • @SeppJ sagte in Array übergeben zu Konstruktor:

    Ihr beide klingt so, als wäre generischer Code für euch kein riesengroßer Vorteil, der jedwede andere Überlegung hinfällig macht.

    Du klingst so, als ob man ohne generischen Code nicht anfangen könnte, Programmieren zu lernen.

    Für mich:

    1. vector lernen
    2. andere Containerklassen erlernen
    3. zeigen, dass man bei allen Containern mit Iteratoren loopen kann
    4. lernen Templates zu schreiben
    5. Funktionen mit Iteratoren erstellen

    Für dich (überspitzt):

    1. generischen Code lernen
    2. vector einführen

    Es ist nicht so, dass ich nie die 2-Iterator-Lösung verwende. Aber den Kontext, in dem eine Frage gestellt wurde, darf man auch nicht ignorieren. Templates (und dir brauchst du, eine Funktion mit zwei std::vector<int>::const_iterator-Paramtern ist ja recht sinnfrei) sind nun mal nicht einfach. Klar muss man die irgendwann benutzen können, wenn man C++ richtig lernen will.

    Es hängt übrigens auch sehr stark vom Umfeld ab, in dem du arbeitest. Wenn das alles Informatiker sind, ist es was anderes als wenn es Leute aus anderen Bereichen sind, die aber programmieren müssen. Und da zählt das Argument "einfacher verständlich" mehr als "generisch". (das ist auch ein wesentlicher Grund, warum ich inzwischen zu 90% Python schreibe).



  • @SeppJ sagte in Array übergeben zu Konstruktor:
    ...

    ??? Warum sollten die internen Strukturen Templates sein?
    ...

    Schon gut...


  • Mod

    @wob sagte in Array übergeben zu Konstruktor:

    @SeppJ sagte in Array übergeben zu Konstruktor:

    Ihr beide klingt so, als wäre generischer Code für euch kein riesengroßer Vorteil, der jedwede andere Überlegung hinfällig macht.

    Du klingst so, als ob man ohne generischen Code nicht anfangen könnte, Programmieren zu lernen.

    Nee, ist schon Ok, das man das nicht als erstes lernt. Aber wenn man's einem Anfänger vormacht, dann bitte nicht falsch, oder wenigstens mit einem großen Exclaimer dass das nur zu Lernzwecken dient. Code, der in C++ eine explizite Containervariante vorschreibt um eine Menge von Daten zu übergeben, ist einfach falsch. Das muss so auch gesagt werden und jedem bewusst sein.

    Es hängt übrigens auch sehr stark vom Umfeld ab, in dem du arbeitest. Wenn das alles Informatiker sind, ist es was anderes als wenn es Leute aus anderen Bereichen sind, die aber programmieren müssen. Und da zählt das Argument "einfacher verständlich" mehr als "generisch". (das ist auch ein wesentlicher Grund, warum ich inzwischen zu 90% Python schreibe).

    Aber gerade in Python wird doch überall Generik praktiziert und es eine der großen Stärken der Sprache, dass es dort so einfach ist! Man muss ja nicht einmal darüber nachdenken, es ist der Default, dass alles komplett generisch ist. Um so schlimmer (und ein bekanntes Antipattern!) ist in Python Code, der irgendwelche Typprüfungen macht. Jeder Best-Practices-Guide für Python sagt, möglichst niemals Konstrukte wie if isinstance(variable, list) zu nutzen. Stell dir mal vor, sum würde nur mit list funktionieren. Würde niemals jemand so schreiben!



  • Du meinst falsch im gleichen Sinne wie falsch, wenn eine Funktion einen std::vector zurückgibt, anstatt einen Output Iterator zu erwarten, der beschrieben wird?
    Sorry, das ist lächerlich. Ne, nichtmal sorry, das ist einfach nur lächerlich.