Verschiedene Klassen durch Vererbung in einem std::vector speichern
-
Hallo,
Ich bin grade dabei eine kleine Game Engine mit OpenGL zu basteln. Im Grunde will ich erstmal mein OpenGL Programm aus meinem letzten Semester erweitern.
Was ich versucht habe zu implementieren ist ein Standard c++ Vektor:
std::vector< GLObject > glObjectsGLObject soll eine superklasse für sämtliche Grafikobjekte sein.
Ich habe also noch eine Klasse GLTriangle die von GLObject erbt, aber die render() Methode überschreibt um ein Dreieck anzuzeigen
void GLTriangle::render() { glBegin(GL_POLYGON); glColor4fv(this->color4fv); GLfloat hoehe = 1.0f*extent; GLfloat c = 1.0f*extent; glVertex3f(-(c/2), -(hoehe/2), 0.0f); glVertex3f(+(c/2), -(hoehe/2), 0.0f); glVertex3f(0.0f, +(hoehe/2), 0.0f); glEnd(); }Mein Problem ist, das wenn ich durch den Vektor iteriere und alle render() Funktionen aufrufe, nichts auf dem Bildschirm erscheint! vereinfacht:
std::vector< GLObject > glObjects; GLTriangle test; glObjects.push_back(test); glObjects[0].render();ändere ich den Vektor zu einem std::vector< GLTriangle > geht wieder alles.
Allerdings will ich ja keinen Vektor für Dreiecke, sonder beliebige Objekte!GLObject sieht wie folgt aus:
#ifndef GLOBJECT_H #define GLOBJECT_H #include <OpenGL/OpenGL.h> class GLObject { public: GLObject(); ~GLObject(); void render(); void animate(); void translatef(); void rotatef(); private: GLfloat extent; GLfloat color4fv[4]; }; #endif //GLOBJECT_Hin der cpp habe ich dann alle Methoden leer implementiert
void GLObject::render() { }da diese sowieso überschrieben werden
Ich nehme an der Fehler beruht auf einem simplen Missverständnis meinerseits, aber ich komme einfach nicht darauf wie ich den Code reparieren könnte.
-
Hi,
Polymorphie funktioniert in deinem Falle nur über Pointer/Referenzen.
Zudem wird eine Funktion nur überschrieben, wenn sie als virtuell deklariert war.Du hast also eine Basisklasse GLObject mit einer Render-Funktion. Hier kannst du keine allg. Implementierung bereitstellen, da dies die unteren Klassen machen sollen, also deklarierst du render als virtuell, genauer pure virtuell(=0):
virtuell void render() = 0;Mit dieser rein abstrakten Methode wirde deine Klasse auch abstrakt, somit kannst du keine Objekte mehr von dieser erzeugen.
Nun willst du in Abhängigkeit des Types im vector die richtige Funktion aufrufen und genau dazu ist virtuell im Zusammenspiel mit Pointer/Referenzen da:
GLObject *base = new GLTriangle(); base->render(); // ruft Triangle::render auf // ... base = new GLBlub(); base->render(); // ruft Blub::render aufSomit musst du in deinen vector GLObject*'te stecken, da alle abgeleiteten Klassenzeiger in den Basisklassenzeiger konvertiert werden können (Up-Cast).
Die weiteren Probleme die durch die Zeiger im Container entstehen, seien hier erstmals nicht erwähnt

-
Nicht zu vergessen: Destruktor entweder virtuell oder protected deklarieren.
Man sollte auch nicht leichtfertig new/delete benutzen und meinen, dass man bei der manuellen Verwaltung der Lebenszeit keine Fehler macht. Es gibt bessere Alternativen -- zB die Boost Pointer Container Library, oder schlaue Zeiger (shared_ptr, clone_ptr).
Es geht möglicherweise auch völlig anders und nicht weniger elegant ohne Laufzeitpolymorhie. Als ich noch in der "von Java nach C++ umsteig"-Phase war, habe ich noch viel zu viele abstrakte Klassen (Interfaces) gebaut, weil das der einzige Abstraktionsmechanismus war, den ich von Java kannte.
Viel Spaß beim C++ proggen!
Gruß,
SP
-
Vielen Dank für die Hilfe soweit!
Ich habe jetzt die Sache mit den Virtuellen Methoden hinzugefügt und es geht soweit auch.
this->glObjectsVector.push_back( new GLDreieck ); this->glObjectsVector.push_back( new GLViereck ); this->glObjectsVector.push_back( new GLKuchen ); for( std::vector<GLObject*>::const_iterator it = glObjects.begin(); it != glObjects.end(); ++it) { (*it)->render(); }Jetzt hab ich ein anderes logisches Problem.
Wenn ich nur Pointer in den Vektor speichere, wo speichere ich dann jeweils das "Original"?In meinem kleinen Code oben ist das ja kein Problem weil ich die Objekte in einer Klasse erzeuge die eh das komplette Programm über läuft, aber was ist wenn ich mich entschließe in einer anderen temporären Methode ein Objekte zum Vektor hinzuzufügen? dann ist die Methode vorbei und ich hab in meinem Vektor einen Pointer der ins Nirwana zeigt...
Ich hab mir jetzt eine (immer laufende) Speicher-Klasse gebaut in der nochmal ein Vektor ist in der ich die Kopien eines Objekts speichern kann und dann einen Pointer daraus in den glObjectsVector speichere.
Das ganze in der Hoffnung das ich immer überall dynamisch ein Objekt einfügen kann ohne Speicherzugriffs Probleme zu bekommen.Leider waren meine ersten versuche nicht sehr erfolgreich, da er wieder nur die Funktionen der Superklasse aufruft und nicht die überschriebenen

GLObject * base = new GLTest; this->glObjectsVector.push_back(StorageArea::getInstance()->addGLObject(base));StorageArea.cpp:
static std::vector< GLObject > kopien; GLObject* StorageArea::addGLObject(GLObject* glOb) { kopien.push_back((*glOb)); return &kopien[kopien.size()-1]; }PS: Bei diesem vorgehen muss ich auch auf die Puren Virtuellen Methoden im GLObject verzichten und habe einfache virtuelle Methoden!
Ich verstehe auch nicht so recht warum es nur mit Pointern und Referenzen funktioniert, wenn ich eine Kopie des Objekts speichere müsste er sich doch auch deren überschriebenen Code merken, und nicht einfach auf die Methoden der Elternklasse zurückgreifen?
-
pad01 schrieb:
Ich habe jetzt die Sache mit den Virtuellen Methoden hinzugefügt und es geht soweit auch.
this->glObjectsVector.push_back( new GLDreieck ); this->glObjectsVector.push_back( new GLViereck ); this->glObjectsVector.push_back( new GLKuchen ); for( std::vector<GLObject*>::const_iterator it = glObjects.begin(); it != glObjects.end(); ++it) { (*it)->render(); }Du meintest sicherlich
(*it).render();.pad01 schrieb:
Jetzt hab ich ein anderes logisches Problem.
Wenn ich nur Pointer in den Vektor speichere, wo speichere ich dann jeweils das "Original"?siehe meinen Kommentar bzgl Boost Pointer Container, schlauen Zeigern und einer eventuell komplett anderen und nicht weniger eleganten Lösung ohne Polymorhie.
pad01 schrieb:
In meinem kleinen Code oben ist das ja kein Problem weil ich die Objekte in einer Klasse erzeuge die eh das komplette Programm über läuft, aber was ist wenn ich mich entschließe in einer anderen temporären Methode ein Objekte zum Vektor hinzuzufügen? dann ist die Methode vorbei und ich hab in meinem Vektor einen Pointer der ins Nirwana zeigt...
Nur, wenn das Objekt ein "automatisches" Objekt war. (Du kennst die drei Speicherklassen "auto", "Freispeicher" und "statisch", ja?)
pad01 schrieb:
GLObject * base = new GLTest; this->glObjectsVector.push_back(StorageArea::getInstance()->addGLObject(base));StorageArea.cpp:
static std::vector< GLObject > kopien; GLObject* StorageArea::addGLObject(GLObject* glOb) { kopien.push_back((*glOb)); return &kopien[kopien.size()-1]; }Ich glaube, Du hast die Sache mit der Polymorphie und der Indirektion noch nicht 100% verstanden. Dieser Code hat zwei Probleme: Es findet Slicing statt. Die Zeiger, die Du zurückgibst, werden nach einer Vergrößerung des Vektors ggf ungültig.
pad01 schrieb:
PS: Bei diesem vorgehen muss ich auch auf die Puren Virtuellen Methoden im GLObject verzichten und habe einfache virtuelle Methoden!
Das habe ich mir gedacht. Dass der Compiler sich beschwert, wenn Du abstrakte Objekte anlegen willst, hätte Dich warnen sollen.
pad01 schrieb:
Ich verstehe auch nicht so recht warum es nur mit Pointern und Referenzen funktioniert, wenn ich eine Kopie des Objekts speichere müsste er sich doch auch deren überschriebenen Code merken, und nicht einfach auf die Methoden der Elternklasse zurückgreifen?
Du speicherst aber keine Kopie des Objektes. Der Vektor kennt den dynamischen Typen gar nicht und weiß auch nicht, wie er solche polymorphen Objekte mit unterschiedlichen aber unbekannten Größen korrekt klonen und abspeichern kann. Es findet Slicing statt.
Polymorphie geht nunmal mit Indirektion Hand in Hand. Das ist bei Java auch nicht anders, da in Java Objekte nie direkt gehalten werden können, sondern immer nur über "Referenzen" erreichbar sind.
Gruß,
SP
-
Sebastian Pizer schrieb:
Du meintest sicherlich
(*it).render();.Nein ist schon richtig so, ich dereferenziere ja erst den iterator und dann den GLObjects pointer.
siehe meinen Kommentar bzgl Boost Pointer Container, schlauen Zeigern und einer eventuell komplett anderen und nicht weniger eleganten Lösung ohne Polymorhie.
Ein anderer Weg fällt so direkt nicht ein, bevor ich aber die Idee aufgebe werd ich noch ein paar Verbesserungsversuche starten

Die Boost Smart Pointer habe ich mir mal angesehen, so wie ich das sehe wäre der shared_ptr meine Lösung oder?
Wenn ich alles so richtig verstanden habe dann sorgt der shared pointer dafür das das Objekt so lange im Speicher bleibt wie ein Pointer darauf zeigt.
Also müsste das Konstrukt bei mir dann ungefähr so aussehen (Habs noch nicht getestet, da ich noch kein boost auf dem Rechner habe)static std::vector< boost::shared_ptr< GLObject > > gloObjectsVector; hinzufügen() { this->glObjectsVector.push_back(boost::shared_ptr< GLObject >(new GLTest)); } ausgeben() { this->glObjectsVector[0]->render(); }Da der shared pointer im Vektor auch nach der hinzufügen() Funktion weiterhin auf das GLTest Objekt zeigt, sollte das Objekt auch im Speicher bleiben, selbst wenn die hinzufügen() Funktion endet. Somit sollte ich bei der ausgeben() Funktion keine Probleme mit dem Speicherzugriff haben, sehe ich das richtig?
Du speicherst aber keine Kopie des Objektes. Der Vektor kennt den dynamischen Typen gar nicht und weiß auch nicht, wie er solche polymorphen Objekte mit unterschiedlichen aber unbekannten Größen korrekt klonen und abspeichern kann. Es findet Slicing statt.
Ah ok, Danke, jetzt versteh ich schon besser was passiert
-
Nein, nimm nicht
shared_ptr, wenn du nur automatisches Aufräumen willst. Da sind die Boost PointerContainer viel besser geeignet.Den Smart Pointer
shared_ptrbraucht man, wenn mehrere Zeiger auf das gleiche Objekt zeigen und nicht genau geregelt ist, wer es freigeben soll (und somit der letzte freigibt). Die ganze Referenzzählung, wodurch dieser Mechanismus implementiert ist, bringt in deinem Beispiel nur unnötigen Overhead mit sich. Mit den Pointer-Containern kannst du ebenfalls Polymorphie einsetzen und hast dabei noch ein freundlicheres Interface (Zeiger werden über Iteratoren direkt dereferenziert), kannst also fast wie mit normalen STL-Containern arbeiten.
-
Danke nochmal, ich hab zuerst die shared pointer eingebaut bevor ich hier weitergelesen habe, aber jetzt hab ich es zu einem Pointer Container umgebaut.
Alles läuft jetzt absolut bestens!
Vielen vielen Dank!static boost::ptr_vector< GLObject > glBoostVector;void GLWorld::addObject(GLObject* glOb) { glBoostVector.push_back(glOb); }void GLWorld::init() { GLObject * base = new GLTest; GLfloat white[] = {255/255.0f, 255/255.0f, 255/255.0f, 1.0f}; base->setColor(white); base->setExtend(5.0f); base->setTranslatef(0, 0, 0); base->setRotatef(90.0f, 0, 0, 1); this->addObject(base); this->addObject(new GLTest); }...boostIter()->render();