Im Konstruktor andere Objekte erzeugen?
-
Hallo,
da ich mal wieder nicht eindeutige Fehlermeldungen bekomme und deshalb nach grundsätzlichen Fehlern im Code suche, wollte ich mal wissen, ob es geht, in einem Konstruktor andere Objekte mit new zu erzeugen:
class Veg{ public: Veg(); //Constructor double i,j,k; }; class VegLandscape{ public: VegLandscape(); Veg* veggrid[xsize][ysize]; }; VegLandscape::VegLandscape(){ for (int i=0; i<xsize; i++) { for (int j=0; j<ysize; j++) { veggrid[i][j] = new Veg(); } } }Geht das, oder ist es besser, die veggrid-Elemente ausserhalb des Konstruktors (im aufrufenden Code) zu initialisieren?
-
Das geht prinzipiell, ist aber nicht exception-sicher. Wenn der Konstruktor von weg oder operator new eine Exception werfen, hast du Speicherlecks.
Sofern du hier keine Polymorphie brauchst, schlage ich stattdessen
std::vector<std::vector<Veg> > veggrid; ... VegLandscape::VegLandscape() : veggrid(xsize, std::vector<Veg>(ysize)) {}oder die Verwendung von Boost.Multi-Array vor.
-
Vielen Dank für die superschnelle Antwort!
Zur ersten Lösung mit Vektoren:
Du meinst wahrscheinlichstd::vector<std::vector<Veg*> > veggrid(also Pointer, wie vorher) und das ganze als Variable innerhalb von VegLandscape wie vorher auch?!
Die zweite Anweisung (Initialisierung) versteh ich ehrlich gesagt nicht so wirklich.
Aber davon abgesehen:
Kann ich bei der ersten Lösung dann genauso mit veggrid[i][j] darauf zugreifen? (sonst hätte ich an ewig vielen Stellen Code zu ändern)Ginge es denn genauso, die Datenstruktur so zu lassen und die Initialisierung des Grids ausserhalb (im Code, der den Konstruktor aufruft) zu schreiben? (muss ich mir selbst noch überlegen, inwiefern das wegen der Sichtbarkeit geht/sinnvoll ist)
-
Bei einem zweidimensionalen Array von besitzenden Pointern auf Veg würde ich das hier nehmen:
#include <array> #include <memory> std::array<std::array<std::unique_ptr<Veg>, ysize>, xsize> veggrid;Allerdings leuchtet mir nicht ganz ein warum du da Pointer verwenden solltest. Na ja, Initialisierung wie gewohn, mehr oder weniger:
veggrid[i][j] = std::unique_ptr<Veg>(new Veg());Freigeben musst du dann nichts mehr, passiert alles automatisch. Aber wie gesagt, was spricht denn gegen:
std::vector<std::array<Veg, ysize>> veggrid;Initialisierung mit
: veggrid(xsize) {}Zugriff geht überall einfach mit [x][y], da musst du nichts ändern. Nur aufräumen musst du bei beiden Varianten nicht mehr, das passiert automatisch, und exception-sicher ist es auch.
-
Ich hab die ganzen Verschachtelungen und wo ich was aufrufen muss, nicht verstanden. Habs deswegen angefangen, es jetzt so abzuändern:
VegLandscape::VegLandscape(){ for (int i=0; i<xsize; i++) { for (int j=0; j<ysize; j++) { veggrid[i][j] = 0; } } } VegLandscape::initialiseVegGrid(){ for (int i=0; i<xsize; i++) { for (int j=0; j<ysize; j++) { veggrid[i][j] = new Veg(); } } }Die neue Methode initialiseVegGrid() rufe ich dann unmittelbar nach der Erzeugung von VegLandscape auf. Geht das so oder gibt das wieder Probleme?
-
susie schrieb:
Zur ersten Lösung mit Vektoren:
Du meinst wahrscheinlichstd::vector<std::vector<Veg*> > veggrid(also Pointer, wie vorher) und das ganze als Variable innerhalb von VegLandscape wie vorher auch?!
Nein. Das meint er wahrscheinlich nicht. Das ist ja gerade der Witz an seiner Antwort, dass da keine Sternchen auftauchen. Gibt es einen besonderen Grund, warum du die Veg-Objekte per new anlegen willst und nicht direkt im Container speichern willst?
-
Ich vermute eine Java-Schädigung. Wie dem auch sei, Cookys Einwand mit std::array ist richtig; da xsize und ysize zur Compilezeit feststehen, gibt es wenig Grund, die Arrays nicht lokal zu halten. Wenn Zeiger wirklich gewollt sind, sollte es auf jeden Fall std::unique_ptr oder std::shared_ptr sein; besitzende nackte Zeiger sind böse. Alternativ ginge ein Boost Pointer Container.
Wie dem auch sei, deine zweite Variante mit der ausgelagerten Initialisierung ist genau so exception-unsicher wie die erste. Du hast damit nichts gewonnen.
-
susie schrieb:
Die neue Methode initialiseVegGrid() rufe ich dann unmittelbar nach der Erzeugung von VegLandscape auf. Geht das so oder gibt das wieder Probleme?
Das ist so ziemlich die schlechteste Methode, auf die man kommen könnte.
-
Ok, war ein Versuch, weil ich das zumindest verstanden hätte. Aber wenn das genauso unsicher ist, bringt es natürlich nichts.
Das mit den Pointern würd ich deswegen ungern ändern wollen, weil es an vielen Stellen so wäre, und ich befürchte, dass ich bei so umfassenden Änderungen wieder viele neue Fehler einbaue. MAg die Änderungen lieber erstmal übersichtlich halten. ODer führt da kein Weg dran vorbei?
Okay, dann mach ich mich an den Ansatz von cooky. Allerdings weiß ich nicht, ob das bei mir überhaupt geht, verwende BCB5. Hab jetzt nur mal probeweise ne Deklaration und dazu die include-Befehle hingeschrieben, da kommt die Fehlermeldung "include-Datei 'array' kann nicht geöffnet werden".
-
BCB5 ist über ein Jahrzehnt alt. Der beherrscht noch nicht mal den alten Standard, geschweige denn den neuen.
Leg dir einen zeitgemäßen Compiler zu, sonst wird das nichts. Visual Studio Express gibt's für lau, ansonsten gibt es massenhaft IDEs, die MinGW benutzen (beispielsweise CodeLite).
-
Das stimmt natürlich, nur verwende ich ein fachspezifisches GUI-Programm, das nur für den BCB geschrieben wurde, und dessen spezielle AUsgaben ich zur Auswertung von meinen Programmdurchläufen brauche.
Gibts da keinen workaround? Ich meine, solche Probleme muss es doch vor 10 Jahren auch schon gegeben haben?!
-
std::array kannst du einfach selbst schreiben. std::unique_ptr theoretisch auch, aber dafür braucht's Move-Semantik, und die gibt's erst seit C++11. Muss deine Klasse da kopiert werden? Ansonsten könnte man nämlich sowas machen:
// my_array.hpp // einfach #ifndef MY_ARRAY_INCLUDED #define MY_ARRAY_INCLUDED template <typename T, std::size_t Size> class my_array { T data_[Size]; public: T& operator [] (std::size_t i) { return data_[i]; } const T& operator [] (std::size_t i) const { return data_[i]; } // .. Für den Rest der Implementierung siehe http://en.cppreference.com/w/cpp oder so // implementier davon halt das was du brauchst }; #endif// my_smart_ptr.hpp // Geht eigentlich nicht vernünftig mit dem alten C++, aber C++11 hast du ja nicht #ifndef MY_SMART_PTR_INCLUDED #define MY_SMART_PTR_INCLUDED template <typename T> class my_smart_ptr { T* p_; public: explicit my_smart_ptr(T* p = 0) : p_(p) {} ~my_smart_ptr() { delete p_; } T& operator * () { return *p_; } const T& operator * () const { return *p_; } T* operator -> () { return p_; } const T* operator -> () const { return p_; } T* get() { return p_; } const T* get() const { return p_; } // Rest nach Bedarf private: my_smart_ptr(const my_smart_ptr&); my_smart_ptr& operator = (const my_smart_ptr&); }; #endifshared_ptr könnte man vermutlich auch mit altem C++ schreiben, aber dafür bin ich jetzt zu faul.
Ansonsten mein Tipp: Alles neu schreiben und einen ordentlichen Compiler benutzen.
-
Dann schreib halt
struct VegLandscape { Veg veggrid[xsize][ysize]; };Einen eigenen Konstruktor brauchst du dann nicht mal mehr; der Default-Konstruktor erledigt das.
-
@cookie:
Vielen Dank für Deine (und der anderen) Mühe!
Ich fürchte nur, für sowas sind meine C++-Kenntnisse zu schlecht.
Man kann sich natürlich in sowas einarbeiten, aber das ganze soll eigentlich ne Diss über mein Fach geben und nicht über C++-Programmierung, das schaff ich zeitlich ja gar nicht...
Noch ein anderer Versuch: Wenn ich die Grids tatsächlich nicht mit Pointern, sondern den Objekten selbst realisieren würde, würde das dann gehen?
Falls ja, hab ich noch folgende Fragen (weil es analog noch andere Klassen betrifft):- Wann würden die Objekte erzeugt bzw. wo muss ich deren Instanzvariablen belegen? Reicht das im Konstruktor?
- Die meisten Klassen der Objekte (ich nenn es mal Gridelement-Klassen) haben nur doubles oder doubles,floats und bools als Instanzvariablen. Eine Gridelement-Klasse hat aber auch Pointer auf eine Klassenhierarchie als Instanzvariable, die manchmal belegt sind, manchmal nicht (Nullpointer). Geht das dann auch? Denn dann hätt ich ja in der Gridelementklasse auch wieder Pointer drin...
- Eine der Gridelement-Klassen hat 45 doubles als Instanzvariable. Wenn ich nun ein Grid mit 100x100 oder gar 500x500 nehme, geht das dann speichertechnisch?
DAnke nochmal!
@seldon: Ja genau, das meinte ich grade
-
seldon schrieb:
Dann schreib halt
struct VegLandscape { Veg veggrid[xsize][ysize]; };Einen eigenen Konstruktor brauchst du dann nicht mal mehr; der Default-Konstruktor erledigt das.
Moment mal, heisst
susie schrieb:
- Die meisten Klassen der Objekte (ich nenn es mal Gridelement-Klassen) haben nur doubles oder doubles,floats und bools als Instanzvariablen.
dass da unterschiedliche Veg Objekte in einem Grid sind ?
Mir schiesst gerade Scott Meyers "Never treat arrays polymorphically" aus "More Effective C++" durch den Kopf ...
-
Wenn du das mit den Pointern aufgibst:
struct blah { std::vector<std::array<Veg, ysize>> veggrid; blah() : veggrid(xsize) // Das hier heißt "Initialisierungsliste" {}Wenn du kein std::array hast, kopier einfach meinen Code von oben in eine Datei "my_array.hpp", #include die, und schreibe std::vector<my_array<Veg, ysize>>.
-
cooky451 schrieb:
Wenn du kein std::array hast, kopier einfach meinen Code von oben in eine Datei "my_array.hpp"
Gewinnt man denn da etwas gegenüber der Verwendung von std::vector, außer dem Schutz vor Größenänderungen ?
-
nn schrieb:
Gewinnt man denn da etwas gegenüber der Verwendung von std::vector, außer dem Schutz vor Größenänderungen ?
Ein vector<vector<>> ist kein 2D Array, sondern ein Superset davon. (Superset.. Übermenge? Na ja, sollte klar sein was gemeint ist.)
Ein 2D Array modelliert man also lieber mit einem vector<array<>>. Ein vector<vector<>> ist mehr wie ein Array aus Pointern oder so. Zugegeben, für ihn dürfte das relativ egal sein, aber es ist ja auch nicht besonders viel Aufwand.
-
Ja, ist schon klar.
Ich frage nur mit dem Gedanken an den BCB5 im Hinterkopf. Ich hab damals damit gearbeitet und würde gefühlmäßig dazu tendieren, erstmal das vorhandene zu verwenden.
Old School C++ mag ich ja auch nicht, aber das ist definitv ein Old School Compiler. Und der Linker gibt auch gerne mal den Geist auf ...
-
@nn:
Hm, nein, ich meinte nur, dass es analog zu VegLandscape-Veg noch andere Klassenpärchen gibt. Ich schreibs mal explizit (nur mit den Variablennamen, ohne Methoden) auf:class VegLandscape { Veg* veggrid[xsize][ysize]; }= wie besprochen.
class Landscape { CellInformation* cellInfo[xsize][ysize]; }class WaterLandscape { Soil* soilgrid[xsize][ysize]; }Die Klassen Veg, CellInformation und Soil meinte ich mit "Gridelement-Klassen".
Diese enthalten ihrerseits Instanzvariablen, und zwar:
Veg: 3 doubles
CellInformation: int, double, bitset und 5 Pointer, letztere alle auf unterschiedliche Klassen in einer Klassenhierarchie.
Soil: 45 Variablen, überwiegend doubles.Die 3 Fragen von oben hätt ich dann noch.
Die Antworten danach guck ich mir jetzt noch mal an.