Frage an alle die denken, dynamisches casting ist ein Zeichen von schlechtem Softwaredesign
-
@Dravere
sry
Hmm, wenn ich mir das Ganze ansehe fallen mir zwei negative Dinge auf:
1. Das Teil ist wie bereits erwähnt sehr schlecht erweiterbar. Jede neue Subklasse erfordert eine Manipulation der Basisklasse sowie sämtlicher bereits existierender Subklassen. (Das ist IMHO nicht gerade schönes OOP und wenn der Source von IBoundingVolume sowie sämtlicher Subklassen nicht offenliegt überhaupt nicht mehr erweiterbar).
2. Ich muss jede Methode doppelt implementieren, bspw. in der BoundingSphere muss ich eine Kollision für BoundingBox implementieren und in der BoundingBox eine für die Kollistion mit der BoundingSphere, obwohl die Mathematik 1:1 dieselbe ist.Da frage ich mich doch, ob meine Version mit statischem O(1) Dispatching nicht schöner ist, denn die kann ich erweitern, ohne die Basisklasse und alle bereits existierenden Subklassen manipulieren zu müssen, dafür habe ich halt 2 (böse) dynamic_casts pro Kollisionstest...
-
mgaeckler schrieb:
Nagut, vieleicht ist mein Beispiel suboptimal, mir ist jetzt auf die schnelle nichts besseres eingefallen. Ich habe auch nach 20 Jahren C++ Entwicklung dieses Jahr zum ersten mal überhaupt dieses Konstrukt benutzt. Mal schauen ob ich die Anwendung finde, wo ich das benutzt habe.
mfg Martin
Jetzt weiß ich wieder wo und warum ich das benutzt habe.
Ich habe eine C++ Klassenbibliothek zur XML-Verarbeitung. Eine der Anwendungen, die diese Bibliothek benutzt, braucht nun eine Eigenschaft, die diese Bibliothek nicht liefert. Ich wollte es auch der Bibliothek nicht beibringen, weil es nur diese eine Anwendung war und die Bibliothek möglichst effizient sein sollte. Deshalb ermittelt die Anwendung nun mit Hilfe des dynamic casts welche Objektklasse es gerade bearbeitet.
mfg Martin
-
Ishildur schrieb:
Hmm, wenn ich mir das Ganze ansehe fallen mir zwei negative Dinge auf:
1. Das Teil ist wie bereits erwähnt sehr schlecht erweiterbar. Jede neue Subklasse erfordert eine Manipulation der Basisklasse sowie sämtlicher bereits existierender Subklassen. (Das ist IMHO nicht gerade schönes OOP und wenn der Source von IBoundingVolume sowie sämtlicher Subklassen nicht offenliegt überhaupt nicht mehr erweiterbar).Das Problem halte ich für überbewertet. Mit einem #if #error oder static_assert noch absichern, daß man keine Klassen vergißt, und ich bin zufrieden.
Ishildur schrieb:
2. Ich muss jede Methode doppelt implementieren, bspw. in der BoundingSphere muss ich eine Kollision für BoundingBox implementieren und in der BoundingBox eine für die Kollistion mit der BoundingSphere, obwohl die Mathematik 1:1 dieselbe ist.
Sehe ich nicht. Die, wo der Klassenname des zweiten Objekts größer ist als der des ersten Objekts, leiten einfach weiter and die echten.
Ishildur schrieb:
Da frage ich mich doch, ob meine Version mit statischem O(1) Dispatching nicht schöner ist, denn die kann ich erweitern, ohne die Basisklasse und alle bereits existierenden Subklassen manipulieren zu müssen, dafür habe ich halt 2 dynamic_casts pro Kollisionstest...
Ich sehe einfach keine dynamic_casts. Dein Kern war doch die Funktionszeigertabelle.
class IBase{ enum RealType{KREIS,QUADRAT,ZWICKEL,DREIECK,EI,ANZAHL,}; virtual RealType getRealType()=0; ... bool table[ANZAHL][ANZAHL](IBase*,IBase*)={... ... if(table[item1->getRealType()][item2->getRealType()](item1,item2))...
-
mgaeckler schrieb:
nun eine Eigenschaft, die diese Bibliothek nicht liefert.
Also Designfehler in der Bibliothek. Und der dynamic_cast hat ihn aufgedeckt.
-
Double Dispatching/Multimethoden sind ja nur der Anfang des Kapitels aus dem Buch. Im weiteren Verlauf beschreibt Meyer eine Tabelle aus Funktionszeigern, die manuell aufgebaut werden muss. Ironischerweise wird bei der Implementation wieder ein dynamic_cast verwendet, den wir eigentlich loswerden wollten.
Meiner Meinung nach sollte man Meyer nicht blindlings vertrauen (zumindest was seinen Vorschlag angeht), sondern sich vernünftige Gedanken über seinen Anwendungsfall machen. Wenn abzusehen ist, dass man nie mehr als zwei oder drei verschiedene Typen vergleichen muss lohnt sich das double-dispatching in meinen Augen nicht. Wenn das Design jedoch flexibel und erweiterbar sein soll sollte man sich mit double-dispatching/Multimethoden oder Funktionstabellen auseinandersetzen. Meyer schliesst das Kapitel mit der Feststellung ab, dass es keine einfach Lösung für dieses Problem gibt und man eine Lösung wählen sollte, die am ehesten dem Bedarf entspricht.
-
volkard schrieb:
mgaeckler schrieb:
nun eine Eigenschaft, die diese Bibliothek nicht liefert.
Also Designfehler in der Bibliothek. Und der dynamic_cast hat ihn aufgedeckt.
Mag sein. Der Terminus Fehler bezeichnet für mich eine Eigenart, die geändert werden muß. Nun ist es aber so, daß es häufig vorkommt, daß bestimmte Anforderungen sich gegenseitig ausschließen.
Beispiel: Der Einsatz virtueller Funktionen steigert sicherlich die Wartbarkeit und Wiederverwendbarkeit einer Implementation veringert dafür aber die Effizienz des Programmes. Nun gilt es eben einen Kompromiss zu finden, zwischen diesen beiden Anforderungen.
Es gilt daher wie so oft, wenn eine bestimmte Technik eingesetzt werden soll, muß man/frau auch immer deren Nachteile kennen und dann von Fall zu Fall entscheiden, was sinnvoll ist.
mfg Martin
-
Wenn dynamic_cast klappt, hast Du eh die vtbl im code und den vptr in jedem Objekt. Da wird durch eine weitere virtuelle Funktion nichts langsamer.
-
mgaeckler schrieb:
Mag sein. Der Terminus Fehler bezeichnet für mich eine Eigenart, die geändert werden muß.
Dann ist der Terminus bei Dir falsch belegt, fürchte ich.
Nehmen wir uns mal das Posting von DocShoe. Er schreibt da von einem Herrn Meyer, auf den man nicht so hören soll. Eindeutig ein Fehler. Der gemeinte Mann heißt Meyers. Trotzdem besteht absolut kein Bedarf, das zu ändern.
-
volkard schrieb:
Dann ist der Terminus bei Dir falsch belegt, fürchte ich.
Nehmen wir uns mal das Posting von DocShoe. Er schreibt da von einem Herrn Meyer, auf den man nicht so hören soll. Eindeutig ein Fehler. Der gemeinte Mann heißt Meyers. Trotzdem besteht absolut kein Bedarf, das zu ändern.Vielleicht solltest du dir das Posting mal richtig durchlesen, statt nur auf Details wie Namen zu achten. Ich habe nicht behauptet, immer auf Meyers Techniken zu verzichten, sondern zu überlegen, ob sie für den eigenen Anwendungsfall nützlich und anwendbar sind. Klar kann ich mehrere zig Zeilen Code schreiben, der eine Designschwäche behebt, aber den Code aufbläht und dafür andere Nachteile mit sich bringt. Das stoische Anwenden von Schema F auf Problem P ist nicht immer die beste Lösung.
-
DocShoe schrieb:
volkard schrieb:
Dann ist der Terminus bei Dir falsch belegt, fürchte ich.
Nehmen wir uns mal das Posting von DocShoe. Er schreibt da von einem Herrn Meyer, auf den man nicht so hören soll. Eindeutig ein Fehler. Der gemeinte Mann heißt Meyers. Trotzdem besteht absolut kein Bedarf, das zu ändern.Vielleicht solltest du dir das Posting mal richtig durchlesen, statt nur auf Details wie Namen zu achten. Ich habe nicht behauptet, immer auf Meyers Techniken zu verzichten, sondern zu überlegen, ob sie für den eigenen Anwendungsfall nützlich und anwendbar sind. Klar kann ich mehrere zig Zeilen Code schreiben, der eine Designschwäche behebt, aber den Code aufbläht und dafür andere Nachteile mit sich bringt. Das stoische Anwenden von Schema F auf Problem P ist nicht immer die beste Lösung.
Ich fühle mich rundherum mißverstanden.
-
Gnihihi, dein Beitrag lässt aber auch genug Spielraum für Fehlinterpretationen. Du solltest klarstellen, worin der Fehler liegt, um weiteren Missverständnissen vorzubeugen.
-
volkard schrieb:
Wenn dynamic_cast klappt, hast Du eh die vtbl im code und den vptr in jedem Objekt. Da wird durch eine weitere virtuelle Funktion nichts langsamer.
Doch. Die Programme, die diese Bibliothek benutzen werden größer und zwar alle unabhängig davon, ob sie die Funktionen alle nutzen oder nicht. Das macht die Programme daher zwangsweise langsamer. Ob sich das bemerkbar macht oder nicht, hängt natürlich vom Einzelfall ab. In einem Fall ging es um eine Lasttestanwendung, die möglichst viel Last auf einem Server generieren sollte ohne aber selbst unnötig Overhead zu erzeugen.
mfg Martin
-
DocShoe schrieb:
Gnihihi, dein Beitrag lässt aber auch genug Spielraum für Fehlinterpretationen. Du solltest klarstellen, worin der Fehler liegt, um weiteren Missverständnissen vorzubeugen.
Der Fehler ist das vergessene s im Namen. Das ist einerseits eindeutig ein Fehler und andererseits völlig unwichtig und muß nicht repariert werden. Es war ein naheliegendes Beispiel, um den Terminus "Fehler" ein wenig zu rücken.
-
volkard schrieb:
DocShoe schrieb:
Gnihihi, dein Beitrag lässt aber auch genug Spielraum für Fehlinterpretationen. Du solltest klarstellen, worin der Fehler liegt, um weiteren Missverständnissen vorzubeugen.
Der Fehler ist das vergessene s im Namen. Das ist einerseits eindeutig ein Fehler und andererseits völlig unwichtig und muß nicht repariert werden. Es war ein naheliegendes Beispiel, um den Terminus "Fehler" ein wenig zu rücken.
Wurde mir dann auch klar... du hättest dich aber auch auf "auf den man nicht hören soll" beziehen können, was deinem Beitrag einen ganz anderen Sinn gegeben hätte.
-
mgaeckler schrieb:
Doch. Die Programme, die diese Bibliothek benutzen werden größer
Was wird grösser? Der zusätzliche VTable-Eintrag?
mgaeckler schrieb:
Das macht die Programme daher zwangsweise langsamer.
Aber daran, dass
dynamic_castin vielen Implementierungen sehr langsam ist, denkst du nicht.mgaeckler schrieb:
Ob sich das bemerkbar macht oder nicht, hängt natürlich vom Einzelfall ab. In einem Fall ging es um eine Lasttestanwendung, die möglichst viel Last auf einem Server generieren sollte ohne aber selbst unnötig Overhead zu erzeugen.
Ja, und genau deshalb solltest du messen. Erst recht, wenn es um eine zeitkritische Anwendung geht. Bitte meide nicht virtuelle Funktionen und weiche auf fragwürdige Alternativen wie
dynamic_castaus, weil du das Gefühl hast, virtuelle Funktionen seien langsam.
-
volkard schrieb:
mgaeckler schrieb:
Mag sein. Der Terminus Fehler bezeichnet für mich eine Eigenart, die geändert werden muß.
Dann ist der Terminus bei Dir falsch belegt, fürchte ich.
Nehmen wir uns mal das Posting von DocShoe. Er schreibt da von einem Herrn Meyer, auf den man nicht so hören soll. Eindeutig ein Fehler. Der gemeinte Mann heißt Meyers. Trotzdem besteht absolut kein Bedarf, das zu ändern.Na gut. Es gibt Fehler mit denen man Leben kann. Wir sprachen aber von Designfehlern einer Bibliothek. Darüber würde ich nicht so einfach hinweg sehen wollen.
mfg Martin
-
mgaeckler schrieb:
volkard schrieb:
Wenn dynamic_cast klappt, hast Du eh die vtbl im code und den vptr in jedem Objekt. Da wird durch eine weitere virtuelle Funktion nichts langsamer.
Doch. Die Programme, die diese Bibliothek benutzen werden größer und zwar alle unabhängig davon, ob sie die Funktionen alle nutzen oder nicht. Das macht die Programme daher zwangsweise langsamer. Ob sich das bemerkbar macht oder nicht, hängt natürlich vom Einzelfall ab. In einem Fall ging es um eine Lasttestanwendung, die möglichst viel Last auf einem Server generieren sollte ohne aber selbst unnötig Overhead zu erzeugen.
mfg MartinTja, ohne dynamic_cast hättest Du die rtti-Tables nicht gebaraucht und vielleicht so mehr Platz gespart. Und dynamic_cast kann extram langsam sein. 1k RAM mehr zu benötigen ist keine Schande mehr. Da müßte man schon extrem genau messen können, um einen Effekt auf die Laufzeit nachzuweisen. Und wo soll das hinführen. Verzicht auf die Benutzung von std::string, weil es Anwender geben könnte, die selber kein std::string brauchen und deswegen dann eine kleinere ausführbare Datei hätten?
Die Entscheidung von Dir scheint eine Überlegung zu sein, wo das letzte Promille Speed herausgequetscht werden soll. Dann würde ich mal schauen, ob man schummeln kann. Zum Beispiel
#ifdef MSVC unsigned long ::getTypeID(IBase* self){return *reinterpret_cast<unsigned int*>(self);} //liest einfach den vptr aus. //zerfällt je nach dem zu einem schnellen asm-befehl //oder zu nur einer anderen adressierung eines anderen befehls. #endifedit: Freie Funktion aus der Methode gemacht. WIllst ja nicht in die Klasse eingreifen müssen.
-
Nexus schrieb:
mgaeckler schrieb:
Doch. Die Programme, die diese Bibliothek benutzen werden größer
Was wird grösser? Der zusätzliche VTable-Eintrag?
Die 4 oder 8 Extrabyte der VTable sind natürlich irrelevant. Aber es müssen ja dann auch alle Funktionen dazugebunden werden. Das sind dann schon deutlich mehr als 4 Byte.
Nexus schrieb:
mgaeckler schrieb:
Das macht die Programme daher zwangsweise langsamer.
Aber daran, dass
dynamic_castin vielen Implementierungen sehr langsam ist, denkst du nicht.Doch natürlich. Nur: Die Anwendung, die den dynamic cast benutzt hat, war ein simpler XML-Editor. Die Reaktionszeit war immer noch kürzer als ein Augenschlag des Anwenders dauert, trotz des furchtbaren dynamic casts.
Nexus schrieb:
mgaeckler schrieb:
Ob sich das bemerkbar macht oder nicht, hängt natürlich vom Einzelfall ab. In einem Fall ging es um eine Lasttestanwendung, die möglichst viel Last auf einem Server generieren sollte ohne aber selbst unnötig Overhead zu erzeugen.
Ja, und genau deshalb solltest du messen. Erst recht, wenn es um eine zeitkritische Anwendung geht. Bitte meide nicht virtuelle Funktionen und weiche auf fragwürdige Alternativen wie
dynamic_castaus, weil du das Gefühl hast, virtuelle Funktionen seien langsam.Ich habe nie geschrieben, daß virtuelle Funktionen langsam sind. Ich habe geschrieben, daß sie Overhead erzeugen, der manchmal nicht aktzeptabel ist und ich habe auch ein Beispiel genannt.
Der Lasttest ist in der Lage, das Verhalten von 1000 Benutzern und mehr zu simulieren. Dazu stehen dann aber natürlich nicht 1000 Rechner zu Verfügung. Der Lasttest darf dann natürlich nicht selbst zum Flaschenhals werden, sonst misst man/frau an der falschen Stelle.
mfg Martin
-
mgaeckler schrieb:
Die 4 oder 8 Extrabyte der VTable sind natürlich irrelevant. Aber es müssen ja dann auch alle Funktionen dazugebunden werden. Das sind dann schon deutlich mehr als 4 Byte.
Nicht benötigte Funktionen müssen nicht zwingend gelinkt werden. Das hängt von den Optimierungsmöglichkeiten des Compilers ab. Aber selbst wenn: Glaubst du,
dynamic_castbrauche keinen Code für die Logik des Typ-Bestimmens zur Laufzeit?mgaeckler schrieb:
Doch natürlich. Nur: Die Anwendung, die den dynamic cast benutzt hat, war ein simpler XML-Editor. Die Reaktionszeit war immer noch kürzer als ein Augenschlag des Anwenders dauert, trotz des furchtbaren dynamic casts.
Aber mit dem Overhead der virtuellen Funktion hätte es knapp nicht mehr gereicht, verstehe.
mgaeckler schrieb:
Ich habe nie geschrieben, daß virtuelle Funktionen langsam sind. Ich habe geschrieben, daß sie Overhead erzeugen, der manchmal nicht aktzeptabel ist und ich habe auch ein Beispiel genannt.
Natürlich sind virtuelle Funktionen manchmal unangebracht. Deswegen sind Methoden in C++ standardmässig auch nicht virtuell. Nur ist es etwas fragwürdig, durch eine Annahme bezüglich des Overheads zu einem anderen Sprachmittel zu greifen, das nicht gerade wenige Nachteile hat.
Wenn du nach ausführlichem Profiling zum Schluss gekommen wärst, dass
dynamic_casttatsächlich die bessere Lösung sei, und dass die Umstellung überhaupt relevanten Einfluss auf die Gesamt-Performance hat, dann okay. Aber so wie du es bisher beschrieben hast, scheinen deine Design-Entscheidungen eher auf Annahmen und Überlegungen zu basieren. Und diese können oft falsch sein, weil man die genaue Implementierung nicht kennt, weil etliche Faktoren dazukommen, oder weil man sich generell schnell verschätzen kann.
-
Wie ich das verstanden habe:
Es gibt eine schlanke XML-Bibliothek, wo diese Funktionalität fehlt.Das Programm Lasttest benutzt die Bibliothek und freut sich über das eingesparte Kilobyte. Lasttest soll so schnell wie möglich sein. Lasttest braucht die Funktionalität nicht.
Das Programm XML-Editor braucht die Funktionalität und nimmt einfach dynamic_cast. Der XML-Editor kann beliebig inperformant sein.
Es bleibt, daß ich auch hier dynamic_cast für schlecht halte und lieber die Bibliothek aufmotzen würde. Falls sie aus dem eigenen Hause stammt oder ich sonst einfachen Einfluß darauf habe.
Und es bleibt
die Aussage, dynamisches casten sei ein Zeichen schlechten Softwaredesigns, ist völliger Humbug.
kann man nicht so stehen lassen.