Array übergeben zu Konstruktor



  • Ich rate dazu, statt eines rohen Arrays std::vector zu benutzen, damit erledigen sich alle Probleme von alleine.
    Wenn du tatsächlich gezwungen bist rohe Arrays zu benutzen sieht die Syntax so aus:

    Movie::Movie( std::string const& title, std::string const& director, int arr_length, int* scores ) ...
    

    Das Array zerfällt bei der Übergabe zu einem Pointer, da kann man mit templates zwar noch was basteln, aber es bleibt umständlicher als einfach einen vector zu benutzen. Und ob initializer_lists für rohe Arrays gehen... puh, da bin ich überfragt.

    PS:
    Die Übergabe der strings als non-const Referenzen ist sperrig, normalerweise übergibt man sowas per value (kann dann in den entsprechenden member gemoved werden) oder als const-Referenz. Mit deinem jetzigen Konstruktor ist

    Movie m( "title", "director", 0, nullptr );
    

    zum Beispiel nicht möglich.



  • @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.


  • Mod

    @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.



  • Du kannst, wie meine Vorredner bereits gesagt haben, std::vector benutzen. Wenn Du Dir allerdings über die Größe deines arrays sicher bist, macht tatsächlich std::array Sinn.

    Das sehe dann einfach so aus:

    Movie::Movie(const std::string& title, const std::string& director, const std::array<int, 10>& scores)
    {
      // code
    }
    

    Hintergrund ist der, dass ein std::vector zwar die Vorzüge eines arrays genießt, wie etwa die Zugriffsgeschwindigkeit von O(1), auf der anderen Seite eine "resize" Operation aber durchaus teuer werden kann, wenn dein "array" größer wird als gedacht.

    Es ist auch grundsätzlich nicht verkehrt mit Zeigern zu arbeiten, wenn du den "overhead", mit dem eine Datenstruktur, die aus STL kommt, vermeiden möchtest. Das ist am Ende alles eine Designfrage. Alles hat Vor- und Nachteile.

    Ich empfehle aber generell ein gutes Buch über C++, wo Datenstrukturen vertieft werden.
    Wenn eine Empfehlung hier im Forum erlaubt ist, kann ich folgende Lektüre empfehlen: C++: Das umfassende Handbuch zu Modern C++



  • @PadMad sagte in Array übergeben zu Konstruktor:

    Wenn Du Dir allerdings über die Größe deines arrays sicher bist, macht tatsächlich std::array Sinn.

    ...
    Hintergrund ist der, dass ein std::vector zwar die Vorzüge eines arrays genießt, wie etwa die Zugriffsgeschwindigkeit von O(1), auf der anderen Seite eine "resize" Operation aber durchaus teuer werden kann, wenn dein "array" größer wird als gedacht.

    Ich habe noch keinen Grund dafür gefunden, ein std::array anstelle eines std::vector zu benutzen ... ganz im Gegenteil, bei der Übergabe ist es sperrig, weil immer die Größe mit übergeben werden muss, bzw. eine eigentlich allgemeine Funktion nur mit einem std::array einer ganz konkreten Größe umgehen kann.
    Und wenn ich mir über die Größe sicher bin, dann kann ich auch den std::vector gleich in der passenden Größe konstruieren, so dass keine resize-Operation anfällt.



  • @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.