abgeleitete Objekte dynamisch in einen Vector speichern
-
Hallo liebes Forum,
ich bin absoluter C++ Novize und scheitere gerade an einem wohl einfachen Problem. Ich möchte einen Vector dynamisch mit abgeleiteten Objekten füllen. Mein nicht funktionierender Beispiel-Code sieht wie folgt aus:#include <iostream> #include <vector> class Car{ public: int wheels = 4; }; class Opel : public Car{ public: int persons; Opel(){persons=4;}; }; class Audi : public Car{ public: int rings; Audi(){rings=4;}; }; int main(){ // dynamic array - push_back { std::vector<Car*> cars; cars.push_back( new Opel() ); cars.push_back( new Audi() ); std::cout << cars[0]->persons << std::endl; // has no member Error std::cout << cars[1]->rings << std::endl; // has no member Error } // static array (also does not work) { Car *array[2]; array[0] = new Opel(); array[1] = new Audi(); std::cout << array[0]->persons << std::endl; // has no member Error std::cout << array[1]->rings << std::endl; // has no member Error } };
Ich habe auch schon viel im Netz gelesen und weiß, dass hier der "Object-Slicing-Effekt" statt findet. Leider habe ich keine Lösungsmöglichkeit gefunden.
Könnt ihr mir helfen?
Vielen Dank im Voraus
-
Hallo,
@mawashi sagte in abgeleitete Objekte dynamisch in einen Vector speichern:
Ich habe auch schon viel im Netz gelesen und weiß, dass hier der "Object-Slicing-Effekt" statt findet.
Da hast du aber falsch gelesen. Object-slicing findet nicht statt, da du ja mit Zeigern arbeitest und die sind immer gleich groß.
Dein Problem ist, dass du von einem Car-Zeiger z.B: Opel-Dinge aufrufen willst. Das macht vom Design erst mal wenig Sinn.
Stell dir mal vor, du hast eine user-Abfrage: "Opel oder Audi". Dann wird vielleicht klarer, warum das keinen Sinn macht.Du kannst dein Car mit
(dynamic_cast<Opel*>(cars[0]))->persons
zu einem Opel machen, aber wie gesagt: das Problem ist das Design.
-
@mawashi sagte in abgeleitete Objekte dynamisch in einen Vector speichern:
std::vector<Car*> cars;
Dein Vector enthält eine Menge von Pointern auf IRGENDWELCHE Autos. Das Objekt hinter
car[0]
könnte daher einOpel
, einAudi
oder vielleicht sogar von der MarkeKlapperkiste
sein. Man weiß es nicht, sofern man nur auf diesenvector
schaut. Dass du dort einenOpel
abgelegt hast, weißt nur du, niemand sonst. Sieh es mal so: vielleicht hast du alle deine Autos zur Reparatur an eine andere Funktion gegeben und sie kommen "umgeparkt" zurück, dann wüsstest du nicht mehr, wo nun genau der Audi und wo der Opel steht.Die Idee hinter der Vererbung ist ja gerade, dass du alle Autos, egal welche Marke, gleich behandeln willst. Wenn du dann aber doch an eine spezielle Eigenschaft einer Marke ran willst, dann musst du entweder diese Eigenschaft allen Autos zur Verfügung stellen (warum gibt es in Audi keine Personen?) - oder du merkst dir beim Erstellen gleich, was deine Audis sind und behandelst diese eben speziell. Oder dein Design ist nicht optimal. Die Variante mit
dynamic_cast
geht zwar, aber so ein Code ist in der Regel nicht besonders gut und auch nicht gut wartbar. Denn wenn du jetzt so eine Kaskase "wenn Opel, dann x; wenn Audi, dann y" einbaust, müsstest du das auch bei jeder weiteren neuen Automarke erweitern - das ist nicht wartbar.Und noch was anderes: denk mal darüber nach, ob du nicht besser einen
std::vector<std::unique_ptr<Car>>
verwenden solltest (Speicherleck in deinem Code, am besten gar keinnew
/delete
verwenden!). Aber das ist eine andere Baustelle, die du danach angehen solltest.
-
Hallo,
vielen Dank für eure schnellen und wertvollen Hinweise:
@Jockelx : Dann habe ich das tatäschlich falsch verstanden. Ich dachte bisher immer, dass man einen vector mit Pointer "reserviert". Da der Pointer aber vom Typ der Basisklasse ist, erstreckt sich der mit dem Pointer verbundene Speicherbereich nicht über die abgeleitete Klasse. Nun ja, viel zu lernen ich noch habeTatsächlich ist mein Minimalbeispiel mit den Autos wohl nicht ganz so zielführend. Ich sehe das Design-Problem. Deshalb habe ich jetzt noch einmal mein tatsächliches Problem stark vereinfacht runter programmiert. Prinzipiell geht es darum eine Sammlung von geometrischen Formen zusammenzufassen. Alle geometrischen Formen haben die Funktion "inside" mit der überprüft werden kann, ob ein Punkt x in der Form liegt oder nicht. Dieses Ansammlung von Formen möchte ich gern in einem Vector speichern, über den ich dann später, ohne zu wissen um welche Form es sich genau handelt, iterieren und entsprechend Punkte x_i prüfen kann.
@wob : Vielen Dank auch für den Hinweis auf die Befehlskette std::vector<std::unique_ptr<Car>> .
Damit konnte ich mein Problem zunächst erst einmal so programmieren, dass es durchläuft und das gewünschte Verhalten zeigt:#include <iostream> #include <vector> #include <math.h> #include <memory> class AbstractShape{ public: std::vector<double> m; std::string shape_type="Abstract Shape"; AbstractShape(){}; AbstractShape(std::vector<double> m){this->m=m;}; virtual bool inside(std::vector<double> x)=0; }; class Circle : public AbstractShape{ public: double r; Circle(){}; Circle(std::vector<double> m,double r):AbstractShape(m){this->r=r;this->shape_type="Circle";}; bool inside(std::vector<double> x){ if ( (this->m[0]-x[0])*(this->m[0]-x[0]) +(this->m[1]-x[1])*(this->m[1]-x[1]) - this->r*this->r <0.){ return true; }else{ return false; }; }; }; class Rectangle : public AbstractShape{ public: double w; // width double h; // height Rectangle(){}; Rectangle(std::vector<double> m,double w,double h):AbstractShape(m){this->w=w;this->h=h;this->shape_type="Rectangle";}; bool inside(std::vector<double> x){ if ( fabs(x[0]-this->m[0])-w/2.<0. && fabs(x[1]-this->m[1])-h/2.<0. ){ return true; }else{ return false; }; }; }; int main(){ // FILL std::vector dynamically and try wanted functionality std::vector<double> m1={0.5,0.5}; double r = 0.25; std::vector<double> m2={0.0,0.0}; double w = 0.1; double h = 0.2; std::vector<std::vector<double>> pnts{ {-0.03,0.06}, { 0.5,0.45}, { 1.0,1.0} }; std::vector<std::unique_ptr<AbstractShape>> ass_of_shapes; ass_of_shapes.push_back( std::make_unique<Circle>(m1,r) ); ass_of_shapes.push_back( std::make_unique<Rectangle>(m2,w,h) ); for (auto & pnt: pnts){ std::cout << "==== process new point ====" << std::endl; for (auto & shape: ass_of_shapes){ std::cout << shape->shape_type << " "; std::cout << shape->inside(pnt) << std::endl; }; }; };
Was meint Ihr dazu, ist das so weit okay? Oder gibt es da auch irgendwelche Fallstricke, die ich übersehe?
Vielen Dank noch einmal. Ihr habt mir echt geholfen
-
@mawashi sagte in abgeleitete Objekte dynamisch in einen Vector speichern:
Was meint Ihr dazu, ist das so weit okay?
Ja, viel besser!
Noch ein paar Verbesserungsvorschläge:
// Das ist nicht so gut: std::string shape_type="Abstract Shape"; // weil z.B.: Circle c; c.shape_type="Rectangle"; // Ausserdem speichert jede Instanz diesen String, der eh immer gleich und unveränderlich sein soll. // Alternative: virtual const std::string& getType() const = 0; //und in den Ableitungen dann sowas: virtual const std::string& getType() const override { static std::string s = "Circle"; return s; } // ------------- AbstractShape(std::vector<double> m){this->m=m;}; // besser: Initialisierungslisten AbstractShape(std::vector<double> m) : m{m}{} // ------------- AbstractShape(std::vector<double> m) // besser: den vector als const reference übergeben, statt zu kopieren: AbstractShape(const std::vector<double>& m) // ------------- virtual bool inside(std::vector<double> x)=0; for (auto & pnt: pnts) for (auto & shape: ass_of_shapes) // const! Immer wenn es geht! virtual bool inside(const std::vector<double>& x) const =0; for (const auto & pnt: pnts) for (const auto & shape: ass_of_shapes) // ------------- if ( (this->m[0]-x[0])*(this->m[0]-x[0]) // Vielleicht etwas geschmacksache, aber "this->" sieht man eher selten // ------------- if ( (this->m[0]-x[0])*(this->m[0]-x[0]) +(this->m[1]-x[1])*(this->m[1]-x[1]) - this->r*this->r <0.){ return true; }else{ return false; }; // Auch geschmacksache, aber ein bool kannst du auch direkt zurückgeben, und nicht in ein "if a return true else return false" packen, also direkt "return a". // ------------- std::cout << "==== process new point ====" << std::endl; // ist zwar hier völlig wurscht, aber ich würde mir "std::endl" nicht angewöhnen, sondern std::cout << "==== process new point ====\n"; // ------------- // Kein Semikolon nach "}" in for-schleifen oder if-statements
-
@mawashi sagte in abgeleitete Objekte dynamisch in einen Vector speichern:
class AbstractShape{
public:
std::vector<double> m;
std::string shape_type="Abstract Shape";
AbstractShape(){};Den Konstruktor könntest du möglicherweise auch weglassen (oder
=default
schreiben) er tut ja nichts. Es ist nicht immer sinnvoll, auch "leere" Objekte konstruieren zu können - kann manchmal aber durchaus nützlich sein.
Was du aber weggelassen hast und nicht weglassen darfst, ist der Destruktor. Du musst diesen nämlich in der Basisklasse alsvirtual
markieren, da du ja nachher die konkreten Shapes wieder wegräumen lassen willst und nicht nur dieAbstrctShape
.Also:
virtual ~AbstractShape() = default;
fehlt.Eine Frage: was macht der Vector
m
genau? Ein Array von Zahlen ist nicht sofort klar, was das bedeuten soll. Der Name sollte also besser sein. Warum hat ein Shape eine Anzahl von Zahlen als Member? Du solltest bei jeder öffentlichen Funktion dokumentieren, wozu die Parameter gut sind/was sie tun.Analog das hier:
double w; // width double h; // height
Wozu der Kommentar, wenn du die Variablen auch gleich
width
stattw
undheight
statth
nennen könntest? Zu viele Abkürzungen machen das Programm schlechter lesbar. Zumal der Kommentar dann einfach entfallen kann. Das habe ich schon öfter erlebt: langer Kommentar, was berechnet wird, um einbuchstabige Variablen zu erklären. Besser: Variablen gut benennen, Kommentar entfernen (bzw. Kommentar auf eigentlich interessante Dinge einschränken).Auch liest sich
points
leichter alspnts
. Solange ein Name nicht unvertretbar lang wird oder ständig überall gebraucht wird, würde ich versuchen, nicht an Buchstaben zu sparen, besonders wenn der Name mehr als ein paar Zeilen lang gültig ist.Und was zum Geier ist ein
ass_of_shapes
? Ein Hinterteil von Formen?Und noch was: ein
if (bedingung) return true; else return false;
kannst und solltest du durchreturn bedingung;
ersetzen - kürzer und einfacher!
-
Wow, eure erneuten Kommentare waren für mich noch einmal echt hilfreich. Danke für die C++ Nachhilfe. Allerdings verstehe ich vereinzelte Hinweise nicht komplett.
@Jockelx : In Zeile 14 verwendest du das Wort
static
. Warum? Ich habe das Schlüsselwort nachgeschlagen und herausgefunden, dass mit dem Schlüsselwortstatic
versehene Variablen während der gesamten Lebensdauer iherer übergordneten Einheit erhalten bleiben. Wieso sollte das hier wichtig sein? Bei jedem Aufruf vongetType()
wird die entsprechende Variables
doch neu belegt.
@Jockelx : Ist das Verwenden von Initialisierungslisten (dein Vorschlag in Zeile 21) auch eine Geschmackssache? Für mich ist klarer, dass über den=
Operator in Kombination mitthis
das Argument der Initialisierungsfunktionm
dem gleichnamigen Attribut des Objektes zugeordnet wird. Deshalb habe ich auchthis
oft verwendet, um direkt im Code zu erkennen, was nun ein Attribut des Objektes und was Methodenargumente sind (bei Gleichnamigkeit von Attribut und Methodenargument).
@Jockelx : Warum sollte man kein Semikolon nach "}" in for-Schleifen oder if-Anweisungen setzen? (Fast) jede Anweisung in C++ muss doch mit einem ";" abschließen oder?
@Jockelx : Dein Hinweis const in Verbindung mit dem &-Zeichen wenn immer es geht zu benutzen bezieht sich sicherlich auf die Prämisse, dass referenzieren effektiver als kopieren ist oder?@wob : Den Destruktor habe ich "nachgerüstet" und mir entsprechendes dazu durchgelesen. Es ist ja echt tückisch, dass ein Minimalbeispiel funktionieren kann, im Kontext eines größeren Programms dann aber beim Weglassen von beispielsweise einem ordentlichen Destructor evtl. Probleme verursacht.
Zu deinen Fragen:m
soll der Mittelpunkt der geometrischen Formen sein. Daher die Abkürzungm
wie "midpoint". Genauso ist das mitass_of_shapes
das für "assembly of shapes" steht. Deine Interpretation ist aber auch nicht schlecht
Mein Programmierbeispiel ist ja ein Minimalbeispiel ohne viel Firlefanz. Trotzdem finde ich deine Gedanken zu Kommentaren und Variablenbenennung wichtig. Als Programmierautodidakt und Ingenieur fehlen mir manchmal solche übergreifenden Gedanken.Zu guter letzt habe ich eure Anregungen der (Volständigkeithalber) implementiert:
#include <iostream> #include <vector> #include <math.h> #include <memory> class AbstractShape{ public: std::vector<double> m; AbstractShape(const std::vector<double>& m){this->m=m;}; // vector als const reference übergeben (kein kopieren da kopieren langsamer ist) // use virtual to make this function overloadable for every derived class/object // use keyword const to declare the return value not to be altered // use &-sign to give back a reference (avoid copying) // use keyword const at the end to indicate that this function cannot change a member variable of the class // and makes it a compiler error if it would do so virtual const bool inside(const std::vector<double>& x) const = 0; virtual const std::string& getType() const = 0; // use deconstructor to avoid problems connected to destroyed classes with pointers to the base class virtual ~AbstractShape() = default; }; class Circle : public AbstractShape{ public: double r; Circle(std::vector<double> m,double r):AbstractShape(m){this->r=r;}; // use keyword override to tell the compiler that we excpect to override a virtual method // (this is just a safety feature that throws a compiler error if we are not overriding a virtual method at that stage) const std::string& getType() const override{ // use the keyword static to maintain this variable throughout the entire life of the objects life // but why is this important here??? static std::string s = "Circle"; return s; }; const bool inside(const std::vector<double>& x) const{ bool inside_flag; inside_flag = (this->m[0]-x[0])*(this->m[0]-x[0]) +(this->m[1]-x[1])*(this->m[1]-x[1]) - this->r*this->r <0.; return inside_flag; }; }; class Rectangle : public AbstractShape{ public: double width; double height; Rectangle(std::vector<double> m,double width,double height):AbstractShape(m){this->width=width; this->height=height;}; const std::string& getType() const override{ static std::string s = "Rectangle"; return s; }; const bool inside(const std::vector<double>& x) const{ bool inside_flag; inside_flag = (fabs(x[0]-this->m[0])-width/2.<0.) &&(fabs(x[1]-this->m[1])-height/2.<0.); return inside_flag; }; }; int main(){ // FILL std::vector dynamically and try wanted functionality std::vector<double> m1={0.5,0.5}; double r = 0.25; std::vector<double> m2={0.0,0.0}; double width = 0.1; double height = 0.2; std::vector<std::vector<double>> points{ {-0.03,0.06}, { 0.5,0.45 }, { 1.0,1.0 } }; std::vector<std::unique_ptr<AbstractShape>> shapes; shapes.push_back( std::make_unique<Circle>(m1,r) ); shapes.push_back( std::make_unique<Rectangle>(m2,width,height) ); for (const auto& point: points){ std::cout << "==== process new point ====" << std::endl; for (const auto& shape: shapes){ std::cout << shape->getType() << " "; std::cout << shape->inside(point) << "\n"; std::string a = shape->getType(); std::cout << "Now we change the variable : " << a << " to "; a = "change"; std::cout << a << "\n"; } } };
Leider verstehe ich die Funktionalität des jeweils ersten Schlüsselwortes
const
in Zeile 15 und 16 nicht. Nach Literaturrecherche, sollconst
hier bewirken, dass der Rückgabewert der Funktion einconst
ist und somit nicht veränderbar ist. In meinem Code-Beispiel, wird aber genau das in der Schleife durchgeführt. Was habe ich da falsch verstanden?Viele Grüße
-
@mawashi sagte in abgeleitete Objekte dynamisch in einen Vector speichern:
@Jockelx : In Zeile 14 verwendest du das Wort static. Warum? Ich habe das Schlüsselwort nachgeschlagen und herausgefunden, dass mit dem Schlüsselwort static versehene Variablen während der gesamten Lebensdauer iherer übergordneten Einheit erhalten bleiben. Wieso sollte das hier wichtig sein? Bei jedem Aufruf von getType() wird die entsprechende Variable s doch neu belegt.
s wird nur einmal beim ersten Aufruf erzeugt und danach ohne Kopie zurückgegeben. Du kannst auch eine statische Membervariable nehmen oder meintwegen auch "Circle" als Kopie zurückgeben - das wäre auch kein Untergang. Der wichtigere Punkt war, dass nicht jede Instanz diesen String hält, der eh immer gleich ist und nie geändert werden darf.
@mawashi sagte in abgeleitete Objekte dynamisch in einen Vector speichern:
@Jockelx : Ist das Verwenden von Initialisierungslisten (dein Vorschlag in Zeile 21) auch eine Geschmackssache?
Nein, das ist mehr. Im Fall von float & co macht es keinen Unterschied, aber sobald du Klassen member hast, ändert sich das Verhalten. Dann wird in deinem Fall nämlich erst das Objekt konstruiert (default-Konstruktor) und dann wird eine Zuweisung gemacht. Bei Initialisierungslisten wird nur der Konstruktor aufgerufen. Hat die Klasse des members keinen default-Konstruktor, dann funktioniert deine Variante überhaupt nicht mehr.
@mawashi sagte in abgeleitete Objekte dynamisch in einen Vector speichern:
@Jockelx : Warum sollte man kein Semikolon nach "}" in for-Schleifen oder if-Anweisungen setzen? (Fast) jede Anweisung in C++ muss doch mit einem ";" abschließen oder?
"}" ist aber keine Anweisung.
-
@Jockelx : Ok. Danke für die Erklärungen.
Eine kleine Frage oder Bitte habe ich (leider) noch. Mein Versuch bestand nun darin, die erarbeitete Funktionalität in eine Übergeordnete Container-Klasse zu packen. Leider scheitere ich daran schon wieder
Hier mein neuer Code (der Übersichtlichkeit halber nur mit der Form Kreis):
#include <iostream> #include <vector> #include <math.h> #include <memory> // HEADER template<unsigned int spacedim> class AbstractShape{ public: std::vector<double> m; AbstractShape(const std::vector<double>& m); virtual const bool inside(const std::vector<double>& x) const = 0; virtual const std::string& getType() const = 0; virtual ~AbstractShape() = default; }; template<unsigned int spacedim> class Circle : public AbstractShape<spacedim>{ public: double r; Circle(std::vector<double> m,double r); const std::string& getType() const override; const bool inside(const std::vector<double>& x) const override; }; template<unsigned int spacedim> class RVE{ // representative volume element with shapes public: std::vector<std::unique_ptr<AbstractShape<spacedim>>> shapes; void add_shape( std::unique_ptr<AbstractShape<spacedim>> shape ); }; // DEFINITIONS template<unsigned int spacedim> AbstractShape<spacedim>::AbstractShape(const std::vector<double>& m) : m{m}{}; template<unsigned int spacedim> Circle<spacedim>::Circle(std::vector<double> m,double r):AbstractShape<spacedim>(m){ this->r=r; }; template<unsigned int spacedim> const std::string& Circle<spacedim>::getType() const{ static std::string s = "Circle"; return s; }; template<unsigned int spacedim> const bool Circle<spacedim>::inside(const std::vector<double>& x) const{ bool inside_flag; inside_flag = (this->m[0]-x[0])*(this->m[0]-x[0]) +(this->m[1]-x[1])*(this->m[1]-x[1]) - this->r*this->r <0.; return inside_flag; }; template <unsigned int spacedim> void RVE<spacedim>::add_shape( std::unique_ptr<AbstractShape<spacedim>> shape ){ this->shapes.push_back( shape ); }; // MAIN-PROGRAMM int main(){ double r1 = 0.25; std::vector<double> m1={0.0,0.0}; std::vector<std::vector<double>> points{ {-0.03,0.06}, { 0.5,0.45 } }; const unsigned int dim=2; RVE<dim> rve; rve.add_shape( std::make_unique<Circle<dim>>(m1,r1) ); };
Kommentire ich die Zeile 57 aus, kompiliert das Programm ohne Probleme. Bei Verwendung der push_back-Methode funktioniert das Ganze leider nicht mehr.... habt ihr eine Idee?
-
@mawashi sagte in abgeleitete Objekte dynamisch in einen Vector speichern:
Kommentire ich die Zeile 57 aus, kompiliert das Programm ohne Probleme. Bei Verwendung der push_back-Methode funktioniert das Ganze leider nicht mehr.... habt ihr eine Idee?
Du hast als Parameter einen
std::unique_ptr<AbstractShape<spacedim>> shape
genommen. Es darf immer nur einen Pointerhalter mit diesem Pointer geben (unique), sonst müsstest du einen shared_ptr verwenden. Du musst hier explizit sagen, dass du den unique_pointershape
weitergeben möchtest. Du musst dazustd::move
verwenden (shapes.push_back(std::move(shape));
) (einenunique_ptr
kann man nicht kopieren, sondern nur verschieben) Danach ist die shape-Variable erstmal in einem "moved-from" state, d.h. du darfst danach nicht mehrshape
dereferenzieren.
-
Ok. Damit klappt es. Vielen Dank!
Aber warum klappt
std::cout << rve.shapes[0]->r << "\n";
dann nicht?
Vielleicht ist es eh keine gute Praxis direkt auf die Attribute zuzugreifen, aber ich hätte nun gedacht, dass man so auf alle "public" Attribute und Methoden des Objectes Circle zugreifen kann...
-
@mawashi Aber dein
rve.shapes
ist doch ein Array von (Pointern auf)AbstractShape
, nichtCircle
!Nur der dynamische Typ ist
Circle
, aber der statische Typ istAbstractShape
.
-
@wob : Man sieht ich habe es noch nicht ganz verstanden
Was ich möchte ist das Folgende: In der KlasseRVE
sollen im vectorshapes
Formen verschiedenen Typs gesammelt werden. Diese sollen alle die Methodeinside
beinhalten um damit in einer Methode derRVE
-Klasse "global" abfragen zu können ob irgendein Punkt innerhalb irgendeiner Form liegt oder nicht. Zusätzlich möchte ich aber auch auf individuelle Methoden der Formen zugreifen können. Für einen Kreis oder eine Ellipse wäre die Methodecurvature
denkbar, die für ein Dreieck oder Rechteck keinen Sinn ergibt. Für ein Dreieck oder ein Rechteck wäre die Methodenumber_of_vertices
möglich, die für Kreise oder Ellipse keinen Sinn ergibt.
Daher wäre es wichtig, auch auf die individuellen Attribute und Methoden der Formen zugreifen zu können und nicht nur auf die derAbstractShape
- Klasse.
-
Off-Topic: Ich würde empfehlen
std::vector
nicht für 2D/3D/4D Vektoren zu verwenden. Das ist bloss umständlich und auch vergleichsweise schrecklich langsam. Typischerweise verwendet man dafür spezielle Klassen alaVector2D
,Vector3D
etc. - die dann direkt die Komponenten als Member haben. Bzw. wenn's schnell gehen muss und man keine passende Library zur Hand hat die das bereitstellt kann man dazu auch gutstd::array
verwenden.
-
@mawashi Was wäre denn, wenn in deinem
shapes
vector 3 Kreise und 4 Rechtecke wären und du dann in einer Schleifeshape->radius
ausgeben wolltest - das würde ja für die Rechtecke nicht funktionieren. Außerdem ist die Idee ja gerade, dass ein vector vonAbstractShape
das auch gar nicht zu wissen braucht, der behandelt alles wieAbstractShape
.Warum sind deine Kreise zusammen mit anderen Formen in diesem Vector? Wir schon geschrieben, du kannst immer einen Downcast machen, aber schön ist das nicht. Wenn du eine bestimmte Funktionalität für alle Shapes einbauen willst und sich die Klassenanzahl nicht oft ändert, sind auch Visitors ein mögliches Pattern. Hier passt das aber eher nicht - du scheinst ja nur wild auf irgendeine Membervariable zugreifen zu wollen.
Vielleicht ist es sinnvoller, eine Liste von Kreisen und eine von Rechtecken separat zu halten.
Wenn du die Funktion "getType" oft brauchst, dann stimmt irgendwas am Design nicht. Wozu haben die Klassen diesen String? Ok, man kann sich damit leicht auf alle Objekte eines konkreten Typs filtern.
Vielleicht kann jemand mit Ahnung von OOP mehr dazu beitragen.
-
@hustbaer : Ok. Danke für den Hinweis. Ich werde den Code noch auf arrays umstellen.
@wob : Eine mögliche Aufgabe später in meinem Code ist es, Abstände zwischen den Formen zu berechnen um den minimalen Abstand zwischen zwei Formen einer Anordnung zu bestimmen. Ich fasse dann also alle Formen einer Anordnung (die irgendwie generiert wird) in dem Vektorshapes
zusammen und iteriere über alle möglichen Paare von Formen. Diese Paare können dann in dem Beispiel also Kreis-Kreis, Kreis-Rechteck oder Rechteck-Rechteck sein. Bei den Abstandsberechnungen zwischen den Formen sind natürlich die Eigenschaften der Formen wesentlich, so dass dann genau einmal über diegetType
Funktion erfragt werden muss, um welche Form es sich handelt. Weiter müssen dann abhängig von der Form die individuellen beschreibenden Parameter wier
beim Kreis oderwidth
undheight
beim Rechteck benutzt werden um die tatsächlichen Abstände zu ermitteln.
-
@hustbaer : bloß wenn ich mit
std::array
arbeite, meine Punkte aber in einemstd::vector
gespeichert sind, dann habe ich doch das Problem, dass die Übergabe zurinside
Funktion beispielsweise nicht funktioniert, da die Funktion einstd::array
erwartet oder?
Ich habe das nach folgendem Schema versucht://... const bool Circle<spacedim>::inside(const std::array<double,spacedim>& x) const{ //.. int main(){ const unsigned int dim=2; double r1 = 0.25; std::array<double,dim> m1{0.0,0.0}; std::vector<std::vector<double>> points{ {-0.03,0.06}, { 0.5,0.45 } }; // .. rve.indicator_bool( points[0] ); rve.indicator_bool( points[0].data() ); rve.indicator_bool( std::array<double,dim>{points[0][0],points[0][1]} ); }
was mir der Compiler mit der Fehlermeldungen quittiert. Einzig der Ausdruck in Zeile 13 funktioniert, sieht aber nicth gerade schön aus...
-
@mawashi sagte in abgeleitete Objekte dynamisch in einen Vector speichern:
std::vector<std::vector<double>> points{ {-0.03,0.06}, { 0.5,0.45 } }; ... rve.indicator_bool( std::array<double,dim>{points[0][0],points[0][1]} );
was mir der Compiler mit der Fehlermeldungen quittiert. Einzig der Ausdruck in Zeile 13 funktioniert, sieht aber nicth gerade schön aus...
Ich hab den Thread jetzt nur überflogen, aber ist hier ein
std::vector<std::array<double, dim>> points = ...
keine praktikable Lösung? Dann sollte eigentlich auchrve.indicator_bool(points[0])
funktionieren.Das dürfte ohnehin performanter sein, da ein
std::vector
immer noch eine Indirektion über einen im Vector gespeicherten Pointer auf die eigentlichen Daten erfordert - im Gegensatz zu einem "flachen"std::array
. Oder eben speziellenVector2D
/Vector3D
-Klassen, welche die Koordinaten direkt als Member haben - bei dem, was ich bisher bei deinem Code sehe, macht das denke ich schon Sinn, zumal ich vermute, dass du im weiteren Code auch einiges an Vektorarithmetik machen wirst.Wenn sowas ordentlich implementiert ist, dann könnte man auch einige Tests eleganter formulieren, z.B. statt
template<unsigned int spacedim> const bool Circle<spacedim>::inside(const std::vector<double>& x) const{ bool inside_flag; inside_flag = (this->m[0]-x[0])*(this->m[0]-x[0]) +(this->m[1]-x[1])*(this->m[1]-x[1]) - this->r*this->r <0.; return inside_flag; };
einfach
template<unsigned int spacedim> const bool Circle<spacedim>::inside(const Vector2D& x) const { auto m_x = m - x; return dot(m_x, m_x) - r * r < 0.0; // dot = Skalarprodukt };
oder auch
transpose(m_x) * m_x - r * r < 0.0
... falls man Vektoren als -Matrizen auffasst und entsprechende Operatoren implementiert sind.
-
@mawashi
Naja, wenn dann müsstest du schon durchgehend mitstd::array
arbeiten.
Alsoconst unsigned int dim=2; double r1 = 0.25; std::array<double,dim> m1{0.0, 0.0}; std::vector<std::array<double,dim>> points{ {-0.03, 0.06}, { 0.5, 0.45} }; // ...
-
@Finnegan : Ja genau, das wäre natürlich ein Traum, wenn man diese Vektor-/Matrixarithmetik so formulieren könnte wie man es händisch aufschreibt. In anderen Sprachen wie Fortran oder Python geht das ja auch recht einfach, aber meines (Anfänger-)Wissens nach müsste man diese Funktionen in C++ auch erst einmal selbst implementieren oder? Das Skalarprodukt ist zwar in der Standardbibliothek über
#include <numeric>
verfügbar, aber wenn ich die Dokumentation richtig verstehe, sieht der entsprechende Befehl dann wie folgt aus:std::inner_product(m_x.begin(),m_x.end(),m_x.begin(),0);
also bei weitem nicht so elegant wie du es aufgeschrieben hast...
@hustbaer : Ok, stimmt, dass hatte ich übersehen. Ich hatte auch versucht aus einem
std::vector<double,dim>
einenstd::array<double,dim>
zu machen, leider ohne Erfolg. Da mir an dieser Stelle Geschwindigkeit noch nicht ganz so wichtig ist, bleibe ich erst einmal beimstd::vector
- Konstrukt um für spätere Eventualitäten flexibler zu bleiben.
-
@mawashi sagte in abgeleitete Objekte dynamisch in einen Vector speichern:
@Finnegan : Ja genau, das wäre natürlich ein Traum, wenn man diese Vektor-/Matrixarithmetik so formulieren könnte wie man es händisch aufschreibt.
Schau dir vielleicht mal eine der LinAlg-Bibliotheken an, z.B. Eigen oder blaze oder oder...
In anderen Sprachen wie Fortran oder Python geht das ja auch recht einfach, aber meines (Anfänger-)Wissens nach müsste man diese Funktionen in C++ auch erst einmal selbst implementieren oder?
In Python ist gibt es sowas auch nicht. Da muss man es auch selbst schreiben oder eine Bibliothek nutzen (wie z.B. numpy oder scipy.linalg). (zu Fortran kann ich nichts sagen)