Datentyp eines Members bei Vererbung

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Benutzeravatar
B.G.Michi
Establishment
Beiträge: 163
Registriert: 07.03.2006, 20:38
Alter Benutzername: B.G.Michi
Kontaktdaten:

Datentyp eines Members bei Vererbung

Beitrag von B.G.Michi »

Guten Tag miteinander
Es wird Zeit, dass ich einige Punkte in meiner Engine ändere, mit denen ich noch nicht zufrieden bin. Allerdings fehlt mir bein manchen die zündende Idee/das richtige Konzept und ich bin mir ziemlich sicher, dass ihr mir helfen könnt :P
Also ich werd einfach mal mit dem ersten, eher allgemeineren Punkt anfagen:

Problem: ich habe eine beliebige Klasse (z.b. ein "class CRenderableObject"). Diese besitzt als Member ein Array einer weiteren Klasse (z.b. "CMesh* m_pMeshes").
Wenn ich jetzt allerdings z.b. im Editor ein "CEditableObject" von meinem "CRenderableObject" ableiten will und dabei aber auch die Meshes "editable" brauche stehe ich vor einem Problem weil ich ja "m_pMeshes" nicht mehr zu "CEditableMesh* m_pMeshes" ändern kann.

Das ist jetzt vlt ein grundlegender Denkfehler von mir... aber ich wüsste gerne ob es dafür ein Patentrezept gibt oder wie man das "normalerweise" löst.

Im moment hab ich mir eine Klasse "CDynamicArray" geschrieben, die erst zur Laufzeit den enthaltenen Typ bekommt und diesen dann im Konstruktor von "CRenderableObject" gesetzt. Im Konstruktor von "CEditableObject" wird dieser dann auf "CEditableMesh" geändert. Das funktioniert soweit auch ganz gut. Allerdings geht mir dadurch im Moment die Typensicherheit flöten und so ganz sauber kommts mir auch ned vor.

Über paar passende Stichworte zum googlen würd ich mich auch freun :)
vielen Dank schon mal
JFF_B.G.Michi
Benutzeravatar
Biolunar
Establishment
Beiträge: 154
Registriert: 27.06.2005, 17:42
Alter Benutzername: dLoB

Re: Datentyp eines Members bei Vererbung

Beitrag von Biolunar »

Templates?

Code: Alles auswählen

template <typename meshT>
class CRenderableObject
{
private:
    typedef meshT mesh_type;

    std::vector<mesh_type> m_Meshes;
};


class CEditableObject : public CRenderableObject<CEditableMesh>
{
};
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: Datentyp eines Members bei Vererbung

Beitrag von Sternmull »

Ich vermute mal das er sein "Renderable" immer vom gleichen Typ haben will, egal ob er es nun im Editor oder wo anders verwendet. Es als Template Klasse mit dem Mesh-Typ als Parameter zu definieren führt aber dazu das er dann auch z.B. seine "Welt"-Klasse oder was auch immer ein CRenderableObject verwendet als Template Definieren müsste wenn er sie mit verschiedenen Mesh-Typen verwenden will.

Wenn man dynamic_cast verwendet könnte man Zeiger auf Renderables verwenden und zur Laufzeit prüfen ob sie sich zu EditableRenderables casten lassen. Das wär dann zar typsicher, aber meinem Gefühl nach nicht das gelbe vom Ei weil man auf RTTI angewiesen wäre.

Statt dessen könnte man mehrere Mesh-Listen pro Objekt anlegen. Eine die immer verwendet wird und Zeiger auf die nicht editierbare Basisklasse der Meshes enthält. Außerdem noch eine Zweite die Zeiger auf die editierbare erweiterte Klasse enthält und nur im Editor befüllt wird (weil die Meshes ja auch nur dort editierbar sind). Das gleiche Konzept könnte man in einer Welt/Szenen-Klasse anwenden. Konkret könnte das etwas so aussehen:

Code: Alles auswählen

// Klassen die Außerhalb vom Editor verwendet werden:

class CMesh
{
	// ...
};

class IRenderable
{
public:
	virtual ~IRenderable(){}
	virtual void Render(IRenderer * renderer) = 0;

	// Verwaltung von nicht editierbaren Meshes
	virtual CMesh * GetMesh(int idx) = 0;
	virtual void AddMesh(CMesh * mesh) = 0;
};

// Erweiterte Klassen die im Editor zu gebrauchen sind:

class CEditMesh : public CMesh
{
	// ...
};

class IEditableRenderable : public IRenderable
{
public:
	virtual ~IEditableRenderable(){}

	// Verwaltung von editierbaren Meshes
	virtual CEditMesh * GetEditMesh(int idx) = 0;
	virtual void AddEditMesh(CEditMesh * mesh) = 0;
};

// Eine "Welt" Klasse die sowohl edtierbare als auch normale Objekte verwalten kann

class CWorld
{
	std::vector<IRenderable *> m_renderables; // ist im Spiel und im Edtior in gebrauch
	std::vector<IEditableRenderable *> m_editableRenderable; // wird nur im Editor befüllt, also nur dann wenn es auch wirklcih IEditableRenderable-Instanzen sind

public:
	IRenderable * CreateRenderable()
	{
		IRenderable * p = new CRenderable();
		// ...

		// wird nur in "m_renderables" eingetragen
		m_renderables.push_back(p);
		return p;
	}

	IEditableRenderable * CreateEditableRenderable()
	{
		IEditableRenderable * p = new CEditableRenderable();
		// ...

		// wird in beide Container eingetragen
		m_renderables.push_back(p);
		m_editableRenderables.push_back(p);
		return p;
	}

	// ...
};
Das würde ich der Template-Lösung vorziehen. Aber ich bin gespannt ob noch bessere Vorschläge kommen.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von Krishty »

Sternmull hat geschrieben:Wenn man dynamic_cast verwendet könnte man Zeiger auf Renderables verwenden und zur Laufzeit prüfen ob sie sich zu EditableRenderables casten lassen. Das wär dann zar typsicher, aber meinem Gefühl nach nicht das gelbe vom Ei weil man auf RTTI angewiesen wäre.
Was ist an RTTI schlimm?
Außerdem ist es tatsächlich die optimale Lösung.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: Datentyp eines Members bei Vererbung

Beitrag von Sternmull »

Krishty hat geschrieben:
Sternmull hat geschrieben:Wenn man dynamic_cast verwendet könnte man Zeiger auf Renderables verwenden und zur Laufzeit prüfen ob sie sich zu EditableRenderables casten lassen. Das wär dann zar typsicher, aber meinem Gefühl nach nicht das gelbe vom Ei weil man auf RTTI angewiesen wäre.
Was ist an RTTI schlimm?
Außerdem ist es tatsächlich die optimale Lösung.
Ich hab nirgends geschrieben das was schlimmes an RTTI wäre. Und die "optimale Lösung" muss es nicht sein. z.B. wenn jemand aus irgend welchen Gründen auf RTTI und Exception Handling verzichten möchte oder muss. Außerdem hat man mit der Ein-Pointer-Container-pro-Typ-Lösung den Vorteil das man effizient über alle Editables iterieren kann. Hätte man nur einen Container müsste man für alle Objekte einen dynamic_cast machen um die richtigen zu finden. Wenn das nur 1 von 100 ist, macht man 99% überflüssige dynamic_casts. Wenn es sich um eine große Menge an Objekten handelt (z.B. in einer Spielwelt) und man oft alle Typen mit einem bestimmten Interface finden muss (Renderable, Updateable, Touchable, Serializable, NetworkSyncronized, ...) könnte da schon merklicher Overhead entstehen.
Natürlich kostet das einfügen und entfernen der Zeiger in die verschiedenen Container auch was, allerdings passiert das nur beim Erstellen/Zerstören eines Objektes. Letzten Endes kann das eine oder andere besser geeignet sein, aber ich würde nicht sagen das dynamic_cast mit einem Zeiger-Container "die optimale Lösung" ist. Obwohl sie natürlich verlockend einfach umzusetzen und in vielen Fällen akzeptabel ist.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von Krishty »

Sternmull hat geschrieben:z.B. wenn jemand aus irgend welchen Gründen auf RTTI […] verzichten möchte oder muss.
Die Gründe müssten aber schon wirklich handfest sein. RTTI ist Teil des Standards und als solches eigentlich noch nicht einmal deaktivierbar. Und wer über DLL-Grenzen hinweg auf LaufzeitTypisierung angewiesen ist, ist eh verloren.
Sternmull hat geschrieben:Außerdem hat man mit der Ein-Pointer-Container-pro-Typ-Lösung den Vorteil das man effizient über alle Editables iterieren kann. Hätte man nur einen Container müsste man für alle Objekte einen dynamic_cast machen um die richtigen zu finden. Wenn das nur 1 von 100 ist, macht man 99% überflüssige dynamic_casts. Wenn es sich um eine große Menge an Objekten handelt (z.B. in einer Spielwelt) und man oft alle Typen mit einem bestimmten Interface finden muss (Renderable, Updateable, Touchable, Serializable, NetworkSyncronized, ...) könnte da schon merklicher Overhead entstehen.
Der dynamic_cast besteht meines Wissens nach aus Vererbungstiefe × einer Adressauflösung (deren Ergebnis nach dem ersten Vergleich für die 99 folgenden im Cache liegt) und einem Zeiger-Vergleich. Selbst, wenn man zehn virtuelle Klassen ineinander verwebt dürfte der Overhead kaum größer sein, als die vtable-Initialisierung einer pur virtuellen Basisklasse nicht wegoptimiert zu haben – also alles andere als merklich. Man kann C++’ RTTI vieles vorwerfen, aber sicher keine Overhead-Probleme … (okay, der Overhead wird nicht durch den Standard beschrieben sondern ist implementation-defined – aber die aktuellen Compiler sind halt eben durch die Bank hocheffizient, wenn es darum geht.)
Sternmull hat geschrieben:Natürlich kostet das einfügen und entfernen der Zeiger in die verschiedenen Container auch was, allerdings passiert das nur beim Erstellen/Zerstören eines Objektes.
Die Template-Lösung kostet in dem Augenblick millionen Mal mehr als die Zeiger-Lösung, in dem man was ändert und eine Stelle vergisst. Da Container mit unterschiedlichen Template-Parametern auch unterschiedliche Typen sind, schlägt der Vorteil sehr bald in einen Nachteil um und aus
for(auto it = GameObjects.begin(); it < GameObject.end; ++it)
    it->Update();

wird ganz schnell
for(auto it = RenderableObjects.begin(); it < RenderableObject.end; ++it)
    it->Update();
for(auto it = PhysicalObjects.begin(); it < PhysicalObjects.end; ++it)
    it->Update();
for(auto it = NetworkSyncdObjects.begin(); it < NetworkSyncdObjects.end; ++it)
    it->Update();

, und die Wartbarkeit ist dahin.
Sternmull hat geschrieben:Letzten Endes kann das eine oder andere besser geeignet sein
Wann was besser geeignet ist, ist geradezu banal entscheidbar: Wird zur Kompilierzeit (statisch) typisiert, nimmt man Templates. Wird zur Laufzeit (dynamisch) typisiert, nimmt man Polymorphie.
B.G.Michi hat geschrieben:Im moment hab ich mir eine Klasse "CDynamicArray" geschrieben, die erst zur Laufzeit den enthaltenen Typ bekommt […]
Aber vielleicht könnte man das Problem, statt es zu lösen, auch einfach ungeschehen machen, indem man die Programmstruktur auf statisch typisierte Speicherung umstellt – das hier lässt vermuten, dass die Wurzel des Übels viel tiefer liegt:
B.G.Michi hat geschrieben:[…] und diesen dann im Konstruktor von "CRenderableObject" gesetzt. Im Konstruktor von "CEditableObject" wird dieser dann auf "CEditableMesh" geändert. Das funktioniert soweit auch ganz gut. Allerdings geht mir dadurch im Moment die Typensicherheit flöten und so ganz sauber kommts mir auch ned vor.
RTTI von Hand – Gratulation, du hast das Rad neu erfunden. Und eckig.

Edit14: Kann mir ein Moderator den Ändern-Button sperren? Ich editier mir hier die Locken glatt. Bitte immer erst antworten, wenn meine Beiträge eine halbe Stunde gegoren sind.
Zuletzt geändert von Krishty am 06.11.2010, 16:21, insgesamt 1-mal geändert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
B.G.Michi
Establishment
Beiträge: 163
Registriert: 07.03.2006, 20:38
Alter Benutzername: B.G.Michi
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von B.G.Michi »

Also erst mal Dankeschön für die schnellen Antworten

Das mit den Templates is an sich ne super Idee... einige Nachteile wurden allerdings auch schon angesprochen... und außerdem würden damit sehr sehr viele (darunter einige der größten) Klassen meiner Engine zu Templates werden.
Die Lösung mit 2 Listen finde ich jetzt nicht wirklich besser, als das was ich im Moment verwende. Vor allem weil es irgendwie unkomfortabel zu programmieren is :)

Mein Problem mit der RTTI-Lösung ist, dass der Typ ja *eigentlich* zur compiletime schon feststeht, mir fehlt blos die Möglichkeit dem Compiler das irgendwie zu verklickern. Außerdem läuft meine Engine im Moment auch auf Symbian C++ und da wird soweit ich das jetzt nachgeschaut habe RTTI nur teilweise unterstützt.
Bisher verwende ich nur ein "*(CEditableMesh*)(GetVoidPointer(nArrayElement))". Mit RTTI wäre zumindest die Typensicherheit zur Laufzeit wieder hergestellt. Ich denke ich werde das einfach mal an nem kleinen Stückchen Code testen und dann Bericht erstatten.

JFF_B.G.Michi

ps:
Krishty hat geschrieben:RTTI von Hand – Gratulation, du hast das Rad neu erfunden. Und eckig.
You made my Day :P. Ich bin aber nicht ganz sicher ob du mich richtig verstanden hast. Aber wie/wo soll ich dem "Container", der ja in CRenderableObject als Member steckt mitteilen welchen Typ Meshes er enthält?

noch was:
Krishty hat geschrieben:Und wer über DLL-Grenzen hinweg auf LaufzeitTypisierung angewiesen ist, ist eh verloren.
In dem Beispiel kommt "CRenderableObject" aus einer DLL meiner Engine. "CEditableObject" und "CEditableMesh" sind allerdings Teil des Editors. Bin ich jetzt verloren? :P
Ich hab ehrlich gesagt noch nicht wirklich was mit RTTI gemacht von demher kenn ich mich ned wirklich aus, wann und wie es funktioniert und wann ned.
Zuletzt geändert von B.G.Michi am 06.11.2010, 16:28, insgesamt 1-mal geändert.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von Krishty »

B.G.Michi hat geschrieben:Bisher verwende ich nur ein "*(CEditableMesh*)(GetVoidPointer(nArrayElement))"
Wunderbar, dass das überhaupt funktioniert.

Verklicker mir erstmal die Vererbungshierarchie … jedes Mesh ist renderbar und von nun an soll auch jedes Mesh editierbar sein – so weit, so gut, so keine RTTI benötigt.
Ich verstehe nur nicht, warum alles Editierbare von allem Renderbaren abgeleitet wird. Was für Meshes hast du, die nicht editierbar sein sollen? Was erbt jetzt wo?
B.G.Michi hat geschrieben:
Krishty hat geschrieben:Und wer über DLL-Grenzen hinweg auf LaufzeitTypisierung angewiesen ist, ist eh verloren.
In dem Beispiel kommt "CRenderableObject" aus einer DLL meiner Engine. "CEditableObject" und "CEditableMesh" sind allerdings Teil des Editors. Bin ich jetzt verloren? :P
Ich hätte es wissen müssen. Der schlimmste Fall tritt immer ein. Falls wir den Bedarf an RTTI nicht durch clevere Hierarchie wegkriegen: ja. (Lustiger Fakt am Rande: Damit laufen auch unterschiedliche Instanzen derselben Funktion über die Meshes, je nachdem, ob sie im Editor erzeugt wurden oder in der Engine.) Aber danke für die Info; langsam beginne ich zu verstehen, um was es geht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
B.G.Michi
Establishment
Beiträge: 163
Registriert: 07.03.2006, 20:38
Alter Benutzername: B.G.Michi
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von B.G.Michi »

CRenderableObject und CMesh sind Teil der eigentlichen Engine und vorerst nicht editierbar.
Im Editor erbt jetzt CEditableMesh von CMesh (da ein editierbares Mesh ja auch ein Mesh "ist") und eben CEditableObject von CRenderableObject
Im Spiel werden aber weiterhin CRenderableObject und CMesh verwendet, da hier ja nichts editiert werden muss.

Das war jetzt auch nur ein Beispiel. Ich habe dieses "Konstrukt" öfter (vlt zu oft) verwendet.
wie gesagt: mir kam das *etwas* unsauber vor. Desshalb wollt ich mal fragen wie ich es besser machen kann.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von Krishty »

Was für Daten-Member (nicht Funktions-Member) fügen CEditableMesh und CEditableObject denn jeweils CMesh und CRenderableObject hinzu?

Etwas unsauber trifft es gut – das Konstrukt ist von der Vererbungshierarchie her eigentlich nicht realisierbar, schon allein deshalb, weil du nicht nur eine erbende Klasse erweiterst sondern auch noch ihre Basisklasse. Wie gesagt, dass es funktioniert, liegt 80-50 zwischen Wunder und Zufall.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: Datentyp eines Members bei Vererbung

Beitrag von Sternmull »

Krishty hat geschrieben:
Sternmull hat geschrieben:z.B. wenn jemand aus irgend welchen Gründen auf RTTI […] verzichten möchte oder muss.
Die Gründe müssten aber schon wirklich handfest sein. RTTI ist Teil des Standards und als solches eigentlich noch nicht einmal deaktivierbar. Und wer über DLL-Grenzen hinweg auf LaufzeitTypisierung angewiesen ist, ist eh verloren.
Deaktivierung von RTTI wird z.B. von GCC und MSVC (ich vermute mal auch von allen anderen Compilern die RTTI anbieten) unterstützt. Das mit den DLL-Grenzen lag mir auf der Zunge, hab es aber nicht angesprochen weil ich auf die Schnelle keinen entsprechenden Beleg ergoogeln konnte. Aber da scheint es wohl Probleme zu geben. Letzten Endes spricht doch aber beides dafür das es sehr wohl einen Grund gibt sich nicht auf RTTI zu verlassen. z.B. sind Ogre und QT beides große C++ Bibliotheken die normalerweise dynamisch gelinkt werden.
Man kann C++’ RTTI vieles vorwerfen, aber sicher keine Overhead-Probleme …
GCC zu -fno-rtti hat geschrieben: Disable generation of information about every class with virtual functions for use by the C++ runtime type identification features (`dynamic_cast' and `typeid'). If you don't use those parts of the language, you can save some space by using this flag.
MSDN zu MSVCs /GR hat geschrieben:However, /GR increases the size of the .rdata sections of your image. If your code does not use dynamic_cast or typeid, /GR- may produce a smaller image.
Das kann man schon als Overhead bezeichnen. Also dürfte RTTI zumindest die Leute stören die an einer minmalen Größe ihrer Binaries interessiert sind. Das Argument mit dem Cache finde ich außerdem nicht sonderlich überzeugend. Ich hatte ja schon geschrieben das ich von der dynamic_cast Lösung im Normalfall keine spürbaren Performanceprobleme erwarte. Aber letzten Endes ist es eben doch so das man zusätzliche ifs und dynamic_casts braucht und potentiell viele Schleifendurchläufe darauf verschwendet Objekte auf Interfaces zu prüfen die sie nicht unterstützen. Ob das zum Problem wird hängt davon ab wie oft man es macht.
Die Template-Lösung kostet in dem Augenblick millionen Mal mehr als die Zeiger-Lösung, in dem man was ändert und eine Stelle vergisst. Da Container mit unterschiedlichen Template-Parametern auch unterschiedliche Typen sind wird aus
for(auto it = GameObjects.begin(); it < GameObject.end; ++it)
    it->Update();

nämlich ganz schnell
for(auto it = RenderableObjects.begin(); it < RenderableObject.end; ++it)
    it->Update();
for(auto it = PhysicalObjects.begin(); it < PhysicalObjects.end; ++it)
    it->Update();
for(auto it = NetworkSyncdObjects.begin(); it < NetworkSyncdObjects.end; ++it)
    it->Update();

, und die Wartbarkeit ist dahin.
Da verwechselst du was: An meinem Lösungsvorschlag sind keine Templates beteiligt. Von der Template-Lösung von Biolunar hab ich explizit abgeraten.

Außerdem würden die Schleifen anders aussehen:

Code: Alles auswählen

for(auto it = RenderableObjects.begin(); it < RenderableObject.end(); ++it)
    it->Render(renderEngine);
for(auto it = PhysicalObjects.begin(); it < PhysicalObjects.end(); ++it)
    it->UpdatePhysics(timeStep);
for(auto it = NetworkSyncdObjects.begin(); it < NetworkSyncdObjects.end(); ++it)
    it->SendNetorkSyncedState(networkThingy);
Denn es geht ja nicht um eine allgegenwärtige Update-Methode sondern um die spezielle Funktionalität die nur von einigen Objekten unterstützt wird. Würde es nur um ein generisches Update() geben hätte man auch hier nur eine Schleife die über einen Container iteriert in dem alle Objekte eingetragen sind die über das Inderface mit dieser Methode verfügen (von mir aus IUpdateable).

Mit dynamic_cast würden die Schleifen so hier enden:

Code: Alles auswählen

BOOST_FOR_EACH(IObject * obj, objects)
{
	IRenderable * rendereable = dynamic_cast<IRenderable*>(obj);

	if (renderable)
		renderable->Render(renderEngine);

	IPhysical * pyhsical = dynamic_cast<IPhysical*>(obj);

	if (physical)
		physical->UpdatePhysics(timeStep);

	INetworkSynced * networkSynced = dynamic_cast<INetworkSynced*>(obj);

	if (networkSynced)
		networkSynced->SendNetorkSyncedState(networkThingy);
}
Das ist nicht besser wartbar. Durch die ifs find ich es sogar hässlicher.

Die Wartbarkeit meiner Lösung hat ein anders Problem: Man muss dafür sorgen das die Objekte auch brav alle betreffenden Container mit ihren Zeigern befüllen und sie auch wieder daraus entfernen. Wahrscheinlich sollte man dafür Iteratoren auf diese Einträge im Objekt speichern um sie schnell wieder entfernen zu können. Aber bis mir Jemand bewiesen hat das es dafür keine schöne Lösung gibt halte ich an meinem Lösungsvorschlag fest :)
Wann was besser geeignet ist, ist geradezu banal entscheidbar: Wird zur Kompilierzeit (statisch) typisiert, nimmt man Templates. Wird zur Laufzeit (dynamisch) typisiert, nimmt man Polymorphie.
Polymorphie kann man aber auch ohne RTTI verwenden. Und ob man mit oder ohne auf RTTI basierendem dynamic_cast besser kommt und welche Vor- und Nachteile man mit oder ohne hat scheint doch nicht ganz so banal entscheidbar. Sonst würden wir wohl nicht drüber diskutieren.
Benutzeravatar
B.G.Michi
Establishment
Beiträge: 163
Registriert: 07.03.2006, 20:38
Alter Benutzername: B.G.Michi
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von B.G.Michi »

Also in dem konkreten Fall halten sich die Datenmember in Grenzen. Es kommen z.b. Zeiger auf die Entries in den Listen des Editors dazu, oder Dateinamen, wo das Objekt ursprünglich auf der Festplatte gespeichert war oder beim Mesh auch die Vertexliste (in CMesh existiert nur der Vertexbuffer). Hier könnte man die Datenmember vlt schon wo anderst hinschaufeln aber an anderen Stellen ist das eher schwierig möglich und war auch nicht ganz das Ziel.

Ich hab es ursprünglich desshalb so gemacht weil es "intern" vlt unsauber ist aber recht einfach zu verwenden. Ich setze im Konstruktor einmal den Typ der Meshs und von da an kann ich sie ganz "normal" verwenden.
Mich würde auch mal interessieren wie ihr sowas macht oder wie ihr eure Vererbungshierarchie anlegt um überhaupt nicht an diesen Punkt zu kommen.
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: Datentyp eines Members bei Vererbung

Beitrag von eXile »

Vielleicht verstehe ich die Hierachie falsch, vielleicht ist meine Idee blödsinn:

Mach eine neue (von mir aus abstrakte) Basisklasse, von der sowohl CRenderableObject (mit CMesh) als auch CEditableObject (mit CEditableMesh) ableiten. Es könnte aber sein, dass das nur das Problem an eine andere Stelle verschiebt, nämlich ob man nun ein CRenderableObject hat oder ein CEditableObject.

Alternativ könnte man das auch als ein Problem auffassen, dass dem Entity-System zugehörig ist: Ein Entity hat die Eigenschaften Editable und Renderable. Wenn das zu Kompilierzeit feststeht, kann man das auch via Templates lösen.

Nachtrag: Sternmulls Antwort würde ich auch für hochheterogene Objekte vorziehen. Dabei macht es keinen Sinn, die Schnittmenge der Eigenschaften solcher Objekte in eine Basisklasse auszulagern, weil die bis auf ein, zwei Funktionen leer wäre. Die Entscheidung ist hier gerade: Sind die C*Object gerade so heterogen, dass sich das hier anbieten würde?
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von Krishty »

Sternmull hat geschrieben:Das mit den DLL-Grenzen lag mir auf der Zunge, hab es aber nicht angesprochen weil ich auf die Schnelle keinen entsprechenden Beleg ergoogeln konnte. Aber da scheint es wohl Probleme zu geben.
Japp … das Problem ist, dass es keine Möglichkeit gibt, die Typlisten beim Laden eines Modules zusammenzuführen (eben wie bei Vftables generell).
Sternmull hat geschrieben:Das kann man schon als Overhead bezeichnen. Also dürfte RTTI zumindest die Leute stören die an einer minmalen Größe ihrer Binaries interessiert sind.
Ja, klar … statischer Overhead. Fünfzig Bytes für jeden polymorphen Typ, abzüglich aller Exceptions (weil der Compiler dafür RTTI generiert, selbst, wenn es deaktiviert ist). Diese Tipps sind für Fälle gedacht, in denen man generell nirgends im Programm Typinformation braucht – in OPs Fall bin ich mir fast sicher, dass der selbstgeschriebene Verwaltungscode und alle Alternativen am Ende mehr Programmtext generieren, als es der Compiler mit aktiviertem RTTI würde. (Besonders selbstgeschriebene Container, bei denen man nicht bis ins Detail auf Agnostizismus geachtet hat, explodieren manchmal förmlich im .text.) RTTI abschalten spart nur Größe, wenn man es eh nicht benutzt.
Sternmull hat geschrieben:Da verwechselst du was: An meinem Lösungsvorschlag sind keine Templates beteiligt. Von der Template-Lösung von Biolunar hab ich explizit abgeraten.
Upps, tatsächlich. Ja, an den Listen ist nichts auszusetzen außer der Problematik verteilten Besitzes, aber die hast du ja selber auch schon angesprochen. Sorry.
Sternmull hat geschrieben:Polymorphie kann man aber auch ohne RTTI verwenden.
Aber nicht für das Problem des OPs – Upcasts sind nur mit RTTI möglich.
Sternmull hat geschrieben:Und ob man mit oder ohne auf RTTI basierendem dynamic_cast besser kommt und welche Vor- und Nachteile man mit oder ohne hat scheint doch nicht ganz so banal entscheidbar. Sonst würden wir wohl nicht drüber diskutieren.
Wir diskutieren drüber, weil wir alle nicht so wirklich wissen, ob OP nun statisch oder dynamisch typisieren kann, soll und will.
eXile hat geschrieben:Vielleicht verstehe ich die Hierachie falsch, vielleicht ist meine Idee blödsinn:
Das könnte ich auch über jeden meiner Posts schreiben … für mich sieht das so aus, als wollte der OP in seiner Engine die Objekte als CMesh in einer Liste verwalten. Sein Problem ist aber nun, dass der Typ dieser Objekte nicht von der Engine bestimmt wird, sondern von der Editor-DLL, die einen eigenen CEditableMesh-Typ mitbringt und darin ablegt.

Da der Typ von der DLL importiert wird, sehe ich statische Typisierung als unmöglich an. Und da es für die Engine total egal ist, ob da nun ein CMesh oder ein CEditableMesh ist – letzteren Typ kennt sie ja nicht, sonst könnte man gleich statisch linken – würde RTTI auch wieder funktionieren, weil jede Stelle, an der man den Typ eines Meshes auf CEditableMesh testet, in der Editor-DLL liegen muss.
B.G.Michi hat geschrieben:Mich würde auch mal interessieren wie ihr sowas macht oder wie ihr eure Vererbungshierarchie anlegt um überhaupt nicht an diesen Punkt zu kommen.
Meine Objekte erben überhaupt nicht; Plugins wie Editoren bekommen einen privaten Zeiger zugeteilt, mit dem sie machen können, was sie wollen. Der wird bei der Konstruktion des Objektes vom Plugin gesetzt, bei der Destruktion von ihm freigegeben und steht jedes Mal zur Verfügung, wenn ein Plugin – aus welchem Grund auch immer – auf ein Objekt zugreift. Für andere Plugins ist der Zeiger unsichtbar. Das geht nur per Virtualisierung, die ich aber eh brauche, damit Multi-Threading beherrschbar bleibt.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: Datentyp eines Members bei Vererbung

Beitrag von Sternmull »

Sternmull hat geschrieben:Polymorphie kann man aber auch ohne RTTI verwenden.
Aber nicht für das Problem des OPs – Upcasts sind nur mit RTTI möglich.
Aber mit meinen Listen bräuchte er nur noch Polymorphie und keine Upcasts und damit auch keine RTTI.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von Krishty »

Aber die Engine kennt den finalen Typ doch garnicht. (Jedenfalls nehme ich das an, sonst würde der Typ nicht in einer DLL liegen sondern man könnte alles statisch zusammenlinken und sich viel Arbeit sparen – aber grundsätzlich, ja, hast du recht. Ich sag’s auch gern wie eine kleine französische Nutte: Du ’asd grundsädslisch Reschd!)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: Datentyp eines Members bei Vererbung

Beitrag von Sternmull »

Danke :D

In dem Moment in dem das Mesh oder EditableMesh erzeugt wird weiß er ja erst mal was er da hat. Und wenn er in diesem Moment die Listen bestückt kommt er auch später noch für alle Instanzen an alle Interfaces die sie unterstützen. So stell ich mir das zumindest vor.

Allerdings versuch ich grad einen Weg zu finden mit dem man das ein- und austragen der Zeiger in die Listen so weit wie möglich automatisieren kann. Das ist nähmlich der Punkt an dem mir mein Lösungsansatz noch nicht so recht gefällt... und irgendwie ist das auch garnicht so einfach. Falls was brauchbares dabei rauskommt werd ich den Code hier mal einbringen.
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: Datentyp eines Members bei Vererbung

Beitrag von BeRsErKeR »

Wenns die editable Objects/Meshes nur im Editor gibt dann nutze doch einfach die Idee von eXile mit der abstrakten Basisklasse (meinetwegen IRenderableObject). Falls es Probleme mit den DLL-Grenzen gibt, pack die abstrakte Basis-Klasse einfach in eine separate Headerdatei, die sowohl die Engine, als auch der Editor statisch inkludiert. Diese Basisklasse könnte man dann mit einem Template-Parameter für den MeshTyp versehen. CRenderableObject würde dann von IRenderableObject<CMesh> erben und CEditableObject von IRenderableObject<CEditableMesh>. Falls nötig könnte man auch mit den Meshes so verfahren: abstrakte Basisklasse IMesh von denen beide Meshklassen jeweils erben. Alles was beide Objekt- bzw. Meshtypen benötigen verpackst du in der abstrakten Basisklasse und schon kannst du CRenderableObject und CEditableObject ohne Templates und unabhängig voneinander nutzen ohne Code zu duplizieren.
Ohne Input kein Output.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von Krishty »

Von einer gemeinsamen Basisklasse halte ich nichts, weil sie sich nur einmal überschreiben lässt. Falls sich B.G.Michi irgendwann entscheidet, den Renderer genau wie den Editor auszulagern (oder eine Physik-Engine hinzuzufügen), steht er vor dem Problem, dass ein Objekt entweder vom Renderer als CRenderableMesh instanziert werden kann oder vom Editor als CEditableMesh, aber nicht als render- und editierbar (oder physikalisch) gleichzeitig. Das Problem hat er zwar jetzt auch schon, aber wenn wir die Klassenstruktur eh schon umschreiben, sollten wir wissen, was für die Zukunft geplant ist und ob was facettenmäßiges nicht vielleicht besser wäre.

Achja – Michi, setz keinen dieser Vorschläge hier um bevor wir auf einen Nenner gekommen sind. Ich befürchte, dass deine void-Casts alles wie ein Kartenhaus kollabieren lassen, falls du jetzt anfängst, irgendwas virtual zu deklarieren.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
B.G.Michi
Establishment
Beiträge: 163
Registriert: 07.03.2006, 20:38
Alter Benutzername: B.G.Michi
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von B.G.Michi »

Also meine void-Casts stecken alle im Container DynamicArray und ich würde sagen für solche fragilen Kartenhäuser gibt es ja Sicherungskopien. :)
Im Moment tendiere ich dazu, die void-Casts einfach zu dynamic_casts zu ersetzen. Damit müsste ich so in etwa das haben was du mit RTTI bezwecken wolltest (wenn ich das ansatzweise richtig verstanden habe) und erhalte mir dabei die größtmögliche Flexibilität. Auch wenns ned ganz so sauber is, aber ich denke es sollte funktionieren und somit hab ich zumidest zur Laufzeit Typensicherheit.

Aber ja, ich werd mal noch schauen was der Thread hier noch ergibt.
Danke für die Hilfe
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von Aramis »

Sicherungskopien.
Siiiicherungskopien? Du meinst die Dinger, die man vor SVN, Mercurial, Git und Co. –in grauer Vorzeit– mal benutzt hat? :-)
Alexander Kornrumpf
Moderator
Beiträge: 2119
Registriert: 25.02.2009, 13:37

Re: Datentyp eines Members bei Vererbung

Beitrag von Alexander Kornrumpf »

Sternmull hat geschrieben:
Sternmull hat geschrieben:Polymorphie kann man aber auch ohne RTTI verwenden.
Aber nicht für das Problem des OPs – Upcasts sind nur mit RTTI möglich.
Aber mit meinen Listen bräuchte er nur noch Polymorphie und keine Upcasts und damit auch keine RTTI.
Potentiell dumme Frage: Bedeutet Upcast nicht in der Klassenhierarchie nach oben, also in Richtung der Basisklasse, und kann das nicht der Compiler statisch entscheiden (weil es nach oben eben immer geht), während ein downcast der ist für den man dynamic_cast (und dann auch RTTI) baucht?
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von Krishty »

B.G.Michi hat geschrieben:Im Moment tendiere ich dazu, die void-Casts einfach zu dynamic_casts zu ersetzen. Damit müsste ich so in etwa das haben was du mit RTTI bezwecken wolltest (wenn ich das ansatzweise richtig verstanden habe) und erhalte mir dabei die größtmögliche Flexibilität.
Ja, ähm, toll. Ich weiß nicht, ob deine Typen überhaupt polymorph sind – dass du sie bisher per void ineinander casten konntest deutet darauf hin, dass sie entweder nicht polymorph waren, oder dass ihre virtuellen Funktionen zufällig fast identisch waren. Denk also daran, dass zumindest der Destruktor von einer der Basisklassen virtual deklariert sein muss, bevor sich die Hierarchie trennt. Falls das bisher nicht der Fall war, haben wir damit auch noch ein potentiell präsentes Speicherleck beseitigt.

Es wäre schon gut zu wissen, ob die Vermutung, dass nur die Editor-DLL die CEditable*-Typen kennt, richtig war. Und ob du in Zukunft noch mit Editor-ähnlichen Sachen ausbauen willst oder schon alles fertig ist und du nur saubermachen möchtest.
Aramis hat geschrieben:Siiiicherungskopien? Du meinst die Dinger, die man vor SVN, Mercurial, Git und Co. –in grauer Vorzeit– mal benutzt hat? :-)
Jaja, mach du nur. An dem Tag, an dem sich die Maschinen gegen uns erheben, stehst du dumm da, aber Michi und mir uns unser Quelltext ist noch da. Auch, wenn ich dann viel Besseres zu tun habe, als weiterzuproggen. Habe ich eigentlich jetzt schon.
Alexander Kornrumpf hat geschrieben:Potentiell dumme Frage: Bedeutet Upcast nicht in der Klassenhierarchie nach oben, also in Richtung der Basisklasse, und kann das nicht der Compiler statisch entscheiden (weil es nach oben eben immer geht), während ein downcast der ist für den man dynamic_cast (und dann auch RTTI) baucht?
So dumm ist die Frage garnicht – meines Wissens nach war es umgekehrt: Beim Downcast wird zur Basisklasse runterkonvertiert (static_cast), beim Upcast wird zu höherer Komplexität hochkonvertiert (dynamic_cast). Aber sicher bin ich mir nun nicht mehr.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: Datentyp eines Members bei Vererbung

Beitrag von Sternmull »

Tatsache, da hast du Recht (sagt Wikipedia). Ein Glück haben wir es in dem Moment beide gleich falsch verwendet und uns somit richtig verstanden :)
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: Datentyp eines Members bei Vererbung

Beitrag von eXile »

Also entweder hab ich wieder Tomaten auf den Augen, aber in deinem Link steht doch:
Wikipedia hat geschrieben:In object-oriented programming, downcasting or type refinement is the act of casting a reference of a base class to one of its derived classes.
Also gilt:
Downcasting: Basisklasse -> Abgeleitete Klasse
Upcasting: Abgeleitete Klasse -> Basisklasse

Was dann im Widerspruch steht zu:
Kristhy hat geschrieben:Beim Downcast wird zur Basisklasse runterkonvertiert (static_cast), beim Upcast wird zu höherer Komplexität hochkonvertiert (dynamic_cast). Aber sicher bin ich mir nun nicht mehr.
(Nachtrag - mir fällt gerade auf: Sternmull sagt garnicht, wer eigentlich womit Recht hat. Damit ist mein kleines Hirn nunmehr ganz überfordert, und ich höre hier auf.)

Aber ich will jetzt nicht weiter auf Definitionen rumhacken! Viel wichtiger: Hier wurde ein ganz nettes System vorgestellt, mit dem man Entities unterschiedliche Facetten zuordnen kann. Das wäre natürlich auch hier anwendbar. Wenn du Lust hast, sogar zu Kompilierzeit.

Nachtrag:
Das Problem hat er zwar jetzt auch schon, aber wenn wir die Klassenstruktur eh schon umschreiben, sollten wir wissen, was für die Zukunft geplant ist und ob was facettenmäßiges nicht vielleicht besser wäre.
Das denke ich auch.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von Krishty »

Geht um die Diskussion zwischen Sternmull und mir ein paar Posts früher, ob man für polymorphes Vorgehen RTTI braucht. Wir dachten beide, zur upgeleiteten Klasse hieße es Abcast, und haben uns deshalb mit dem falschen Terminus richtig verstanden. Ich werde in diesen OOP-Slang niemals reinkommen :roll:
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
B.G.Michi
Establishment
Beiträge: 163
Registriert: 07.03.2006, 20:38
Alter Benutzername: B.G.Michi
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von B.G.Michi »

Krishty hat geschrieben:Es wäre schon gut zu wissen, ob die Vermutung, dass nur die Editor-DLL die CEditable*-Typen kennt, richtig war. Und ob du in Zukunft noch mit Editor-ähnlichen Sachen ausbauen willst oder schon alles fertig ist und du nur saubermachen möchtest.
sry, hab ich vorher übersehn, aber die Vermutung is richtig: CEditable* stecken im Editor und die Engine kennt sie ned.
Ich würde ned soweit gehen, weder Engine noch Editor als fertig zu bezeichnen und will natürlich noch weiterbasteln. Aber das war eben was dass mich gestört hat.
odenter
Establishment
Beiträge: 207
Registriert: 26.02.2009, 11:58

Re: Datentyp eines Members bei Vererbung

Beitrag von odenter »

Sind saubere Interfaces da nicht die Lösung?

Pseudocode

Code: Alles auswählen

class IMesh { // pure virtual
};

class CMesh : public virtual IMesh { // Implementierung
};

class IEditableMesh  { // pure virtual
};

class CEditableMesh :  public CMesh, public virtual IEditableMesh { // Implementierung
};

class IRenderableObject { // pure virtual
};

class CRenderableObject : public virtual IRenderable { // Implementierung
protected:
  std::list<IMesh*> _meshes;
};

class IEditableRenderableObject { // pure virtual
};

class CEditableRenderableObject : public CRenderableObject, public virtual IEditableRenderableObject { // Implementierung
};
In der Engine castest Du zu IMesh und im Editor zu IEditableMesh, musst halt nur beim erzeuegen sicherstellen das die entsprechend richtigen Objekte erzeugt werden z.B. über ne Factory-Methode die einen Parameter (oder als Template), abhängig davon ob nun der Editor läuft oder die Engine, für den Typ CMesh/CEditableMesh entgegen nimmt und einen IMesh zurück liefert.
Und das gleiche für die CRenderableObject/CEditableRenderableObject.
Hierbei müsstest Du nur beim erzeugen der Objekte prüfen welchen Typ Du brauchst, ob das Compilat dadruch nun besondern aufgepumpt wird oder nicht, dazu kann ich nichts sagen.

Das nachträglich umzubauen ist natürlich nicht unerheblicher Aufwand.
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4263
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: Datentyp eines Members bei Vererbung

Beitrag von Chromanoid »

Ich plädiere auch für
Krishty hat geschrieben:was facettenmäßiges
. Bei Unity3D funktioniert das auch gut und viele andere professionelle Entwickler benutzen das auch (z.b. laut 'making games magazin' die Leute von Deck13).
Benutzeravatar
kimmi
Moderator
Beiträge: 1405
Registriert: 26.02.2009, 09:42
Echter Name: Kim Kulling
Wohnort: Luebeck
Kontaktdaten:

Re: Datentyp eines Members bei Vererbung

Beitrag von kimmi »

Das klingt für mich so, als wäre ein komponenten-basierender Ansatz auch nicht dumm: http://www.gamedev.net/community/forums ... _id=551515 . So erspart man sich die sehr komplex werdende Klassen-Hierarchie und daraus entstehende Probleme wie den Diamond of Deapth oder ähnliches.

Gruß Kimmi
Antworten