Array übergeben zu Konstruktor
-
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ürzenDennoch 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 immerstd::begin()
undstd::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?
Beistd::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!) undinitializer_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.
-
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.
-
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.
-
@DocShoe sagte in Array übergeben zu Konstruktor:
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.
-
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 diestd::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.
-
@DocShoe sagte in Array übergeben zu Konstruktor:
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:
- vector lernen
- andere Containerklassen erlernen
- zeigen, dass man bei allen Containern mit Iteratoren loopen kann
- lernen Templates zu schreiben
- Funktionen mit Iteratoren erstellen
Für dich (überspitzt):
- generischen Code lernen
- 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...