Problem mit Software Architektur (C++)
-
Ein pure specifier ist aber keine Adresse und hat offiziell die Form = 0. Alles andere ist falsch, basta!
Ok, überzeugt, ich ändere es überall
Nein, das ist es 1:1. Sobald ich irgendwo eine virtuelle Vererbung habe stürzt das Teil ab. MSVS 2008
-
1:1
class IResource{ public: virtual int GetValue() const = 0; }; class HeightmapResource:public virtual IResource{ public: int GetValue() const{return 5;} }; int main(){ IResource *pRsc = new HeightmapResource(); delete pRsc; return 0; }
Stürzt beim delete ab, entferne ich das virtual bei der Vererbung von HeightmapResource auf IResource funktioniert alles bestens...
-
Mach mal den Destruktor auch virtuell.
-
JTZT GEHHTERR.... DAS GIBTS DOCH GAR NIDD....
Kannst du mir das erklären, irgendwie sehe ich das nicht so ganz...
-
Jockelx schrieb:
Mach mal den Destruktor auch virtuell.
Wollte es gerade auch schreiben, aber du warst schneller.
Ishildur schrieb:
Kannst du mir das erklären, irgendwie sehe ich das nicht so ganz...
Löscht du ein polymorphes Objekt über einen Zeiger auf seine Basisklasse, so wird logischerweise der Destruktor der Basisklasse aufgerufen. Ist dieser aber nicht virtuell, so wird der Destruktor der abgeleiteten Klasse nicht aufgerufen, was zu allerlei Problemen führen kann.
-
OK, soweit so gut, aber die destruktoren sind doch alle leer?
-
Ishildur schrieb:
Kannst du mir das erklären, irgendwie sehe ich das nicht so ganz...
Ein Destruktor ist grundsätzlich auch nichts anderes wie eine Funktion. Wenn du keinen virtuellen Destruktor hast und auf der Basisklasse das delete aufrufst, so wird nur der Destruktor der Basisklasse aufgerufen und die von den abgeleiteten Klassen nicht. Somit wird das Objekt nur unvollständig zerstört.
Das sind aber absolute C++ Grundlagen
Ist es eigentlich bei dir üblich, dass du des öfteren Klassen hast, welche nur pure virtual Funktionen haben?
Grüssli
-
Ishildur schrieb:
OK, soweit so gut, aber die destruktoren sind doch alle leer?
Der Kompiler kann aber eigener Code reinsetzen, was er auch tut. Um Vererbung, virtuelle Funktionen usw. usf. lauffähig zu bekommen, braucht es normalerweise noch etwas zusätzlichen Code. Solcher Code wird dann auch im Destruktor aufgeräumt, ohne dass du es zu sehen bekommst
Grüssli
-
Ja ich meine auch mehr, wieso es dann nur bei der virtuellen Verwerbung zu einem Problem kommt?
Ist es eigentlich bei dir üblich, dass du des öfteren Klassen hast, welche nur pure virtual Funktionen haben?
Ja, wieso? Nicht gut?
-
Ishildur schrieb:
Ja ich meine auch mehr, wieso es dann nur bei der virtuellen Verwerbung zu einem Problem kommt?
Es kann auch sonst zu Problemen kommen, es ist einfach undefiniertes Verhalten, wenn du es nicht machst. Und undefiniertes Verhalten ist eben undefiniert. Es kann mal funktionieren oder abstürzen, die Welt kann explodieren oder ein Antimaterie-Universum dabei entstehen und somit das unsrige gleich mitreissen und am Ende war nichts mehr ...
Ishildur schrieb:
Ist es eigentlich bei dir üblich, dass du des öfteren Klassen hast, welche nur pure virtual Funktionen haben?
Ja, wieso? Nicht gut?
Kann man sicherlich drüber streiten, aber dein bisherigen Codestil und deine Vorgehensweisen deute ich so, dass du von irgendeiner anderen Sprache schlecht beeinflusst wirst und probierst in C++ nicht C++ zu programmieren. Aber bisher waren es nur Indikatoren. Will also nichts unterstellen
Grüssli
-
@Dravere
Ich lerne gerne dazuAlso ich versuche in C++ objektorientiert und möglichst flexibel zu programmieren, so wie ich das in jeder anderen Sprache ebenfalls versuche. Mein Grundsatz ist, "Programmiere gegen Interfaces nicht gegen Implementationen". Daher die Interfaces. Beispiel: IResource, die sieht bei mir beim aktuellen Projekt folgendermassen aus:
// *********************************** interface "IResource" ********************************** // This interface defines a set of functionalities which must be implemented by every // Resource. // Author: Samuel Lörtscher // ******************************************************************************************** class IResource{ public: // ------------------------------------ interface methods ------------------------------------ virtual void Register(IResourceService *Service,IResourceReader *Reader) = 0; virtual void Load() = 0; virtual void Unload() = 0; virtual void Reload() = 0; virtual string *GetName() const = 0; virtual ResourceFamily GetFamily() const = 0; virtual IResourceService *GetOwner() const = 0; virtual IResourceReader *GetReader() const = 0; virtual uint32 RefCount() const = 0; virtual bool IsLoaded() const = 0; virtual ~IResource(){} // ------------------------------------------------------------------------------------------- }; // ********************************************************************************************
Jedem, der IResource benützt, kann es völlig egal sein, wie IResource diese Dinge intern organisiert. Ich muss bspw. einfach abfragen können, ob das Teil geladen ist oder. Ob er dann das in einem bool speichert, oder seinen Owner fragen muss oder was auch immer ist mir egal...
Dazu kommt der ResourceReader (Auch wieder nur ein Interface [only pure virtual methods]), dieser muss einfach die methoden Open, Read, Size, Close machen können. Ob der noch einen WinSock benötigt oder ein FileHandle oder was weis ich, müssen alle die Programmkomponenten, welche IResourceReader in irgendeiner Form benutzen nicht wissen...
Oder hast du das wegen anderen Dingen gemeint?
-
Mußt nur gelegentlich schauen, ob sich der Aufwand überhaupt gelohnt hat.
-
Es ist halt ein bisschen komisch, da eine Klasse die nur pure virtual Funktionen hat, keinerlei Funktionalität hat. Sie gibt ein paar Namen vor, macht sonst aber nichts was diesen Namen Bedeutung gibt.
Gegenfrage: Inwiefern unterscheiden sich diese beiden Klassen?
class fahrzeug { virtual ~fahrzeug(){} virtual void beschleunigen()=0; virtual void abbremsen()=0; }; class ballaballapingpong { virtual ~ballaballapingpong(){} virtual void fnvgkjengdjkfgvd()=0; virtual void fdjfn4j4nwksdfnkd()=0; };
Gar nicht, oder?
Erst wenn man ein bisschen Funktionalität dazu packt, bekommt die Fahrzeugklasse einen Sinn:
class fahrzeug { virtual ~fahrzeug(){} virtual void beschleunigen()=0; virtual void abbremsen()=0; virtual void einfache_fahrt { beschleunigen(); abbremsen(); } };
-
Ishildur schrieb:
"Programmiere gegen Interfaces nicht gegen Implementationen"
Nur haben die Interfaces in diesem Satz mit den Interfaces in Java nichts zu tun
-
@Ishildur,
Nehmen wir zum Beispiel die MethodeGetName
. Ich gehe mal davon aus, dass jeder Ressource einen Namen hat. Wieso speicherst du dies dann nicht gleich in der Basisklasse ab? Lohnt es sich wirklich hier eine virtuelle Funktion zu haben? Wer wird das schon anders abspeichern? Siehst du da wirklich sinnvolle Einsatzzwecke?
Eine nicht ganz so offensichtliche Überlegung könnte man bei der MethodeReload
machen. Ist ein Reload nichts anderes als einUnload
gefolgt von einemLoad
? Ist es wirklich nötig, dass Reload virtuell ist? Könnte man nicht bereits eine Implementation anbieten, welche halt automatischUnload
und danachLoad
aufruft?
So kann man sich halt jeweils weiterhangeln. Man ist zwar vielleicht sehr flexibel, aber womöglich erhöhst du für dich nur unnötig den Aufwand. Zudem ist da eigentlich noch diese Sache, dass man per Richtlinie keine virtuellen Funktionen als Schnittstelle haben sollte, sondern die virtuellen Funktionen immer private machen sollte. Aber das ist dann wirklich ein StreitpunktAnsonsten siehe noch die Aussagen von SeppJ und Shade Of Mine, dann muss ich dies nicht auch noch hinschreiben
Übrigens: Wieso gibst du bei
GetName
dasstd::string
Objekt als Zeiger zurück? Ist auch einer der Indikatoren, dass du anscheinend ziemlich viel mitnew
anlegst.Grüssli
-
Dravere schrieb:
Zudem ist da eigentlich noch diese Sache, dass man per Richtlinie keine virtuellen Funktionen als Schnittstelle haben sollte, sondern die virtuellen Funktionen immer private machen sollte. Aber das ist dann wirklich ein Streitpunkt
Sorry für den Doppelpost, aber hatte vorhin den Link einfach nicht mehr gefunden dazu:
http://www.gotw.ca/publications/mill18.htmGrüssli
-
Es ist halt ein bisschen komisch, da eine Klasse die nur pure virtual Funktionen hat, keinerlei Funktionalität hat.
Das ist ja genau der Sinn
Stell die folgende Situation vor:
Du hast ein fertiges Projekt. Nun kommt der Chef oder der Kunde und sagt, wir müssen nun noch ein umfangreiches Logging System einfügen, der Zustand von einigen Objekten muss nun geloggt werden können. "LORD, wie soll denn dass gehen, davon hat beim Design der Architektur niemand was gesagt!"Kein Problem:
class ILoggable{ public: virtual string *LogStatus() = 0; }; Logger{ public: void RegisterLoggable(ILoggable *Loggable); };
Nun kann man jedes objekt in der ganzen Applikation Loggable machen, indem es einfach das Loggable interface implementiert, oder in C++ virtuell erbt.
Der Logger kann nun den Status von jedem Objekt in seiner Applikation zu jedem Zeitpunkt loggen ohne diese Objekte in irgendeiner Weise kennen zu müssen, ist das nicht Toll?
-
Weiterer Vorschlag und auch passend zu Dravere's NVI Anmerkung:
Hier sieht z.B. IsLoaded() nicht unbedingt danach aus, dass die pure virtual sein muss.
Gegenvorschlag:class IResource{ public: void Load(); bool IsLoaded(); private: bool loaded; void virtual doLoad(); // Wobei void bei Load komisch ist, regelst du das über Exceptions, wenn's nicht klappt? }; ... void IResource::Load() { doLoad(); loaded = true; // what ever, bzw. besser je nach Rückgabewert setzen } bool IResource::IsLoaded() { return loaded; // what ever }
-
@Dravere
Da hast du zu 100% recht, daher schreibe ich auch immer eine Default Teilimplementierung, bspw. AbstractResource, welche genau diese Aufgaben übernimmt. Für jede Resource kann man schliesslich selbst entscheiden, ob man von der AbstractResource Klasse erben will, welche einen Grossteil der "in den meisten Fällen identisch implementierten Teile" bereits erledigt, oder ob es bessär ist, eine komplett neue Implementation zu schreiben.
-
Ishildur schrieb:
Nun kann man jedes objekt in der ganzen Applikation Loggable machen, indem es einfach das Loggable interface implementiert, oder in C++ virtuell erbt.
Irgendwie hab ich das Gefühl, du hast nicht verstanden, wozu virtual Vererbung in C++ da ist.