Von Java zu C++
-
Hallo,
ich steige gerade von Java auf C++ um und verstehe da noch so manches nicht.
Z.B. musste man ja in Java, wenn man ein Array erstellt hat, jeden Index einzeln initialisieren bzw. einzeln eine Referenz zu einem Objekt ablegen.
Wenn ich in C++ ein Array erstelle, sind bereits alle Indexe Initialisiert bzw. mit einem Wert belegt.
Zunächst mal wäre das ja egal, da ich die Werte ja einfach überschreiben/verändern kann aber ist das nicht unnötige Rechenleistung die da verloren geht, wenn C++ erst unnötiger weise das Array mit dem Standard Konstruktor initialisiert und ich dann alle nochmal überschreibe, da ich zum beispiel eine tile map welt generieren will?
Ich bin da ziemlich verwirrt, da man in Java immer sehr darauf geachtet hat, dass man bloß nicht unnötiger weise eine neue Referenz erzeugt hat, die man hätte vermeiden können(Ich denke es liegt bei Java vor allem an dem GC).
Ich hoffe jemand kann mir helfen und das erklären.
-
Falsch.
Wenn du in Java ein Array (von primitiven typen wie int, double et.c) anlegst ist dir garantiert, dass die alle mit 0 initialisiert werden. Ja, daher etwas verlorene Rechenkapazität.
Wenn du Objekte hast, dann werden auch diese mit 0 initialisiert, d.h. aber in dem Fall mit null.
Es ist also konsistent, in jedem Fall werden 0en geschrieben.In C++ gibt es aber sowas wie Referenzvariablen nicht wie in Java. D.h. du kannst nicht MyClass myObj = null; schreiben (es sei denn, null ist eine selbst definierte Variable...). Und da Objekte nicht in einem undefinierten Zustand in Arrays liegen könne wird halt der Standardkonstruktor aufgerufen.
Bei primitiven Typen jedoch wird nichts initialisiert. Wenn du Glück hast steht genau das drin, was du brauchst oder lauter Nullen oder was auch immer. Ergo, hier wird keine Rechenzeit verbraten.
-
Kommt drauf an, was genau du mit einem Array meinst. Allgemein kann ein Objekt ja nicht existieren, wenn es nicht initialisiert ist. Ich kenne Java nicht so genau, aber das kann dort ja nicht so groß anders sein. Wenn du ein Objekt erzeugst, musst du auch wissen, wie dieses aussieht, sonst hättest du es gar nicht erst erzeugen sollen. Wobei Erzeugen bei POD-Typen (plain old data) in C++ in der Regel bedeutet, nichts zu tun. Und bei komplexen Datentypen, einen Konstruktor zu bemühen, denn wie schon gesagt, kann ein Objekt schließlich gar nicht existieren, ohne konstruiert worden zu sein.
Oder meinst du das Verhalten von std::vector, auch im Fall von PODs alle Objekte mit einem bestimmten Wert zu initialisieren? Das kann in seltenen Fällen wirklich unerwünscht sein, in dem Fall muss man dann zu einem Smartpointer + new greifen (und wohl der einzige Fall, in dem new für Arrays richtig ist).
Oder stammt deine Verwirrung daher, dass Objekte in C++ wirklich "sie selbst" sind, und nicht, wie in Java, eine Referenz auf ein Objekt? Wenn du in C++ ein Array von Zeigern erstellst, dann sind die natürlich auch nicht initialisiert (Zeiger sind POD) und zeigen erstmal ins Nirvana, bis du ihnen etwas zuweist. Aber das ist eine Indirektion, die man in C++ gar nicht nötig hat. Hier arbeitet man in der Regel direkt mit den Objekten, es sei denn, man benötigt die Eigenschaften von Referenzen/Zeigern.
Es ist leider unklar, worauf sich deine Frage genau bezieht, daher ist großes Ratespiel angesagt. Kannst du konkreter werden, was du meinst?
-
In Java reservierst du, wenn du eine int-Array mit 5 Speicherplätzen deklarierst, 5 Speicherplätze für Referenzen welche auf int's im Speicher zeigen. In C++ kannst du das auch machen, jedoch reservierst du mit int asdf[5]; 5 Speicherplätze für int's und nicht nur den Speicher für die Referenzen darauf.
Du solltest dir erstmal klar machen, was genau Referenzen und Pointer in C++ sind, wenn du das dann verstanden hast, kannst du dir auch langsam mal gedanken über Rechenleistung und Speicherverbrauch machen.
-
Mr.Long schrieb:
In Java reservierst du, wenn du eine int-Array mit 5 Speicherplätzen deklarierst, 5 Speicherplätze für Referenzen welche auf int's im Speicher zeigen. In C++ kannst du das auch machen, jedoch reservierst du mit int asdf[5]; 5 Speicherplätze für int's und nicht nur den Speicher für die Referenzen darauf.
Nein, das ist falsch. ints, und doubles und und und sind auch in java keine Referenzdatentypen. Erst die Wrapperklassen (Integer, Double etc.) sind Referenzdatentypen die Verweise brauchen.
-
DuffCola schrieb:
Ich bin da ziemlich verwirrt, da man in Java immer sehr darauf geachtet hat, dass man bloß nicht unnötiger weise eine neue Referenz erzeugt hat, die man hätte vermeiden können(Ich denke es liegt bei Java vor allem an dem GC).
Im Gegenteil. Java hat einen eingebauten Speicherpool, C++ reserviert in vielen Implementierungen neuen Speicher ziemlich oft durch das Betriebssystem und braucht dadurch deutlich länger (habe ich jetzt nicht gemessen, also muss es nicht stimmen).
Im Gegensatz zu Java kann C++ aber fast alle Objekte direkt auf dem Stack erzeugen und das ist deutlich schneller und sollte deswegen auch oft gemacht werden.
Oft hat der Default-Constuctor auch kaum Arbeit zu erledigen, sodass es kaum Unterschied macht, ob initialisiert oder nicht.
-
Marthog schrieb:
Im Gegenteil. Java hat einen eingebauten Speicherpool, C++ reserviert in vielen Implementierungen neuen Speicher ziemlich oft durch das Betriebssystem und braucht dadurch deutlich länger (habe ich jetzt nicht gemessen, also muss es nicht stimmen).
Es stimmt auch nicht, wie seit 10 Jahren regelmäßig gemessen wird. Vom BS wird selten geholt. Selbst wenn es (total veraltet) nur in Packen zu 64k wäre, um 64k sinnvoll vollzuschreibseln, braucht man schon ein Weilchen. Mal synthetische Tests ausgenommen, wo eben nicht sinnvoll mit dem Speicher gearbeitet wird. Und das logische Freigeben muss auch nicht verzögert werden, denn das zu löschende C++-Objekt ist nämlich eh gerade erst angefaßt worden und liegt noch im Cache.
-
Also konkreter meine ich eine Tile Map.
Ich habe eine Tile Klasse die z.B. Typ, Position usw... speichert.
Ich weiß, dass es viel performativere bzw. "bessere" Methoden für Tile Maps gibt, aber darum geht es hier jetzt nicht.
Jetzt habe ich ein Array, dass sämtliche Tiles beinhaltet, auch da gibt es sicherlich noch bessere Lösungen, aber auch darum geht es mir jetzt nicht.
Wenn ich jetzt die Tile Map generieren würde, initialisiert C++ ja das Array erst mit dem Standard Konstruktor.
Und danach würde ich jedes Tile nochmal überschreiben, mit einem anderen Konstruktor wo ich z.B. unterschiedliche Typen von Tiles bestimme.
Und genau das ärgert mich irgendwie.
Was für Optionen bleiben mir?
Ich habe nur diese Alternative gefunden:Tile**tiles= new Tile*[1000]; tiles[0] = new Tile(/*Meine Argumente wie z.B. Type*/);Das wäre doch die performativste oder?
-
DuffCola schrieb:
Ich habe nur diese Alternative gefunden:
Tile**tiles= new Tile*[1000]; tiles[0] = new Tile(/*Meine Argumente wie z.B. Type*/);Das wäre doch die performativste oder?
Bei weitem nicht. Viele new-Aufrufe sind ziemlich langsam. Zudem liegen deine Objekte im Speicher verteilt und können schlecht gecached werden. Außerdem hast du durch die zwei pointer eine zusätzliche Indirektion und das zieht auch nochmal Zeit und macht den Code komplexer.
Es kommt natürlich auch immer auf die Datenstruktur an. Wenn Tile riesig und nicht kopierbar ist, kann dieses hier schon die richtige Variante sein.std::vector<Tile> tiles; // Optional anderer container wie deque und moeglicherweise noch ein reserve Aufruf for (int i=0; i<1000; ++i) { tiles.push_back(Tile(/* hier Argumente*/)); // In C++11: tiles.emplace_back(/* hier Argumente */); }
-
DuffCola schrieb:
... Wenn ich jetzt die Tile Map generieren würde, initialisiert C++ ja das Array erst mit dem Standard Konstruktor.
Und danach würde ich jedes Tile nochmal überschreiben, mit einem anderen Konstruktor wo ich z.B. unterschiedliche Typen von Tiles bestimme.
Und genau das ärgert mich irgendwie.
Was für Optionen bleiben mir? ...Seit C++11 gibt es emplace_back(). Damit sparst den Aufruf des Default-Konstruktors ...
std::vector<Tile> v; v.reserve(1000); // optional v.emplace_back(/* Konstruktor-Parameterliste */);
-
DuffCola schrieb:
Ich habe nur diese Alternative gefunden:
Tile**tiles= new Tile*[1000]; tiles[0] = new Tile(/*Meine Argumente wie z.B. Type*/);Das wäre doch die performativste oder?
Vergiss am besten erst einmal, dass es
newgibt. Das ist in C++ längst nicht so praktisch wie in Java. Du musst nichtnewbenutzen, um Objekte zu erzeugen. Schau mal ins Buch "Accelerated C++" rein, wie idiomatisches C++ aussieht. Wenn Du keinen besonderen Grund hast, die Tile-Objekte einzeln und manuell pernewim Freispeicher anlegen zu müssen, dann lass es.
Spricht was gegen
std::vector<Tile>oderstd::vector<std::vector<Tile>>? Bei ersterem würden die Tile-Objekte alle schön hintereinander im Speicher liegen und du könntest Dir selbst aus einer 2D-Gitterkoordinate einen Index berechnen. Kopieren muss man da auch nichts. Stattpush_backkönntest Du auchemplace_backverwenden:dein_vector.reserve( ...wie gross der vector werden soll... ); for (...) { dein_vector.emplace_back( ...KonstructionsParameter für ein Tile... ); }