Interface für generischen Typ
-
Type Erasure ist da meiner Meinung nach schöner.
class any { struct any_impl_base { virtual ~any_impl_base() {} }; template <typename T> struct any_impl : any_impl_base { T object; any_impl(T obj) : object(obj) {} }; any_impl_base* impl; public: template <typename T> any(T obj) : impl(new any_impl<T>(obj)) {} ~any() { delete impl; } template <typename T> T& get() { return dynamic_cast<any_impl<T>&>(*impl).object; } template <typename T> any& operator = (T obj) { any_impl<T>* ptr = new any_impl<T>(obj); delete impl; impl = ptr; return *this; } }; template <typename T> T& any_cast(any& a) { return a.get<T>(); }Fehlen natürlich noch einige Sachen, aber vom Prinzip her würde ich das so lösen.
-
314159265358979 schrieb:
Type Erasure ist da meiner Meinung nach schöner.
Schöner als was?
Sowohl dein Beispiel als auch meins als auch das von seldon machen Type Erasure.
-
void* hat nicht viel mit Type Erasure zu tun, imo.
-
314159265358979 schrieb:
void* hat nicht viel mit Type Erasure zu tun, imo.
In Java ist das der Standard Weg. Da macht man überall void* wie in seldon Besipiel und nennt sie halt Object-Referenz.
Wieso sollte es also in C++ keine type erasure mehr sein.
(unabhängig jetzt davon welche implementierung hier die schönste ist)
-
Pis Weg löst das Problem mit Besitzverhältnissen recht elegant, wenn man Kopiersemantik braucht - in diesem Fall muss sowieso an den Heap gegangen werden. Braucht man dagegen Referenzsemantik, scheint mir mein Weg sinnvoller - der virtuelle Destruktor bietet dann keine Vorteile, und man spart sich den Overhead der dynamischen Speicherallokation.
Ungeachtet dessen sehe ich eine gewisse Ironie darin, im Zusammenhang mit dieser Anforderung über die Schönheit der Implementation zu sprechen. Allein die Tatsache, dass man etwas derartiges benötigt, deutet auf wichtigere Designprobleme hin.
-
Vielen Dank, hustbaer, seldon & 314159265358979, eure Antworten haben mich weiter gebracht! Kannte Type Erasure nicht bis anhin.
Wie ist das mit Runtime Type Information in Bezug auf typeid respektive dynamic_cast? Ich habe mich da bis jetzt nicht weiter damit beschäftigt, soweit ich weiss wird aber der Compiler bei beiden Varianten RTTI brauchen, also überall im Code, auch wo gar nicht benötigt zusätzlichen Code einfügen, was dann tendenziell die Performance ziemlich beeinträchtigen kann, typeid noch weniger als dynamic_cast?
Wärs da nicht effizienter, da ich die Typeninformationen nur hier lokal benötige im Container für den generischen Typ in den vom Compiler erstellten Templateklassen, diese selber zu implementieren? Also etwas in der Art wie:
class TypeInfo { public: TypeInfo():Number(++OverallNumber){}; const int id() const{ return Number; }; private: const int Number; static int OverallNumber; }; int TypeInfo::OverallNumber=0; class GenericType { class Object { public: virtual const int getTypeId() const =0; }; template<class T> class ObjectInstance:public Object { public: ObjectInstance( T _value ):Value(_value),TypeId(*ClassTypeId){}; T Value; const int getTypeId() const { return TypeId.id();}; static const int typeId(){ return ClassTypeId->id();}; private: const TypeInfo TypeId; static const TypeInfo* const ClassTypeId; }; public: template<class T> GenericType( T _value ){ Content=new ObjectInstance<T>( _value ); }; ~GenericType() { delete Content; }; GenericType( const GenericType& _toCopy ){ /*...*/}; template<class T> T& as() { if( GenericType::ObjectInstance<T>::typeId()==Content->getTypeId() ) { return static_cast< ObjectInstance<T>* >(Content)->Value; } // else throw an exception }; private: Object* Content; }; template<class T> const TypeInfo* const GenericType::ObjectInstance<T>::ClassTypeId=new TypeInfo();?
seldon schrieb:
Ungeachtet dessen sehe ich eine gewisse Ironie darin, im Zusammenhang mit dieser Anforderung über die Schönheit der Implementation zu sprechen. Allein die Tatsache, dass man etwas derartiges benötigt, deutet auf wichtigere Designprobleme hin.
Inwiefern? Scheint mir die optimale Methode um veränderbare Settings, die ganz natürlicherweise unterschiedliche Typen haben können in einer gemeinsamen Klasse mit identischem Zugriff speichern zu können?
Btw. Gäbs boost::any wohl kaum, wenn sowas nur benötigt wird, wenn beim Design was schief gelaufen ist..
-
314159265358979 schrieb:
void* hat nicht viel mit Type Erasure zu tun, imo.
Wieso nicht?
void*ist IMO die ultimative Type-Erasure, viel weniger "Typ" alsvoid*kann man wohl nicht haben
-
@ccquestions:
Die Performance spielt bei dem was du da machen willst IMO keine grosse Rolle.
Ich würde dafür auf jeden Fall keinen Type-Info Code selbst schreiben.
-
hustbaer schrieb:
@ccquestions:
Die Performance spielt bei dem was du da machen willst IMO keine grosse Rolle.Das ist auch die Frage bezüglich des RTTI: Ich weis nicht wie das genau umgesetzt wird vom Compiler etc. Hab ich da nicht, sobald irgendwo mal der typeid header auftaucht im ganzen Programm das Runtime Type Tracing drin, was ja dann völlig idiotisch wäre, da ichs ja wirklich nur an der Stelle da benötige, notabene mit Typen, die bei der Kompilierung ja eigentlich dem Compiler sogar bekannt sind?
hustbaer schrieb:
Ich würde dafür auf jeden Fall keinen Type-Info Code selbst schreiben.
Wenn der typeid der std library das schön umsetzt ja klar, auf der anderen Seite reicht mir hier ja eine simple Integer Id ohne das ganze Getöse rundherum völlig aus, also so wie im Code oben funktioniert das perfekt...
-
ccquestions schrieb:
Hab ich da nicht, sobald irgendwo mal der typeid header auftaucht im ganzen Programm das Runtime Type Tracing drin, was ja dann völlig idiotisch wäre, da ichs ja wirklich nur an der Stelle da benötige, notabene mit Typen, die bei der Kompilierung ja eigentlich dem Compiler sogar bekannt sind?
Runtime type information. Das ist ohnehin standardmässig aktiviert (wegen Exceptions) und normalerweise kein Problem.
ccquestions schrieb:
auf der anderen Seite reicht mir hier ja eine simple Integer Id ohne das ganze Getöse rundherum völlig aus, also so wie im Code oben funktioniert das perfekt...
Warum selbst basteln, warten und konsistent halten? Hast du Angst vor dem Overhead?
-
Nexus schrieb:
Runtime type information.
Weiss ich danke. Mir gehts nur aufn Sack immer die gleichen Wörter zu verwenden
Grundsatz Nr. 1 beim Schreiben 
Nexus schrieb:
Das ist ohnehin standardmässig aktiviert (wegen Exceptions) und normalerweise kein Problem.
Genau hierauf bezog sich die Frage. Das bedeutet also, dass dynamic_cast nur zu Overhead führen kann, weil dann bei Einsatz die ganzen Vererbungen etc durchgegangen werden müssen? Die Informationen sind sowieso alle schon da?
Nexus schrieb:
Warum selbst basteln, warten und konsistent halten? Hast du Angst vor dem Overhead?
Wenn die Informationen sowieso da sind, ist dies natürlich unnötig.
-
ccquestions schrieb:
hustbaer schrieb:
@ccquestions:
Die Performance spielt bei dem was du da machen willst IMO keine grosse Rolle.Das ist auch die Frage bezüglich des RTTI: Ich weis nicht wie das genau umgesetzt wird vom Compiler etc. Hab ich da nicht, sobald irgendwo mal der typeid header auftaucht im ganzen Programm das Runtime Type Tracing drin, was ja dann völlig idiotisch wäre, da ichs ja wirklich nur an der Stelle da benötige, notabene mit Typen, die bei der Kompilierung ja eigentlich dem Compiler sogar bekannt sind?
RTTI hat nur dort Performance-Overhead, wo du es auch verwendest. "Getraced" wird da nix. Normalerweise geht das über den VTable-Zeiger, und den muss es sowieso geben, sobald man virtuelle Funktionen verwendet. Da virtuelle Funktionen Voraussetzung für RTTI sind -> kein Overhead. Ausser eben dort wo man Dinge wie typeid() oder dynamic_cast auch wirklich verwendet.
Beide, also typeid und dynamic_cast, können zwar ein gutes Stück langsamer sein als selbstgebastelte Lösungen, nur da das an der Stelle mMn. wirklich vernachlässigt werden kann -> egal.
EDIT: typeid() selbst ist normalerweise sehr sehr schnell. Was ein Bisschen langsam sein kann ist dann der Vergleich zweier type_info Instanzen. Sollte aber auch nur einen spürbaren Impact haben wenn die Typen nicht gleich sind, oder die type_info Instanzen aus unterschiedlichen Programmteilen kommen (z.B. verschiedenen DLLs). /EDIT
-
ccquestions schrieb:
Inwiefern? Scheint mir die optimale Methode um veränderbare Settings, die ganz natürlicherweise unterschiedliche Typen haben können in einer gemeinsamen Klasse mit identischem Zugriff speichern zu können?
Btw. Gäbs boost::any wohl kaum, wenn sowas nur benötigt wird, wenn beim Design was schief gelaufen ist..Das Problem ist, dass du auf verschiedene Typen keinen identischen Zugriff bekommst, sofern diese nicht von der selben Basisklasse erben, die das Interface bereitstellt - und in diesem Fall ist die ganze Apparatur eh unnötig.
Überleg dir, wie der benutzende Code am Ende aussehen soll bzw. muss - ich habe Schwierigkeiten, mir ein anderes Vorgehen vorzustellen als "Versuch's mit dem Typ. Funzt nicht? Probier's mit dem nächsten.", und das ist alles andere als sauber.
Wie man Konfigurationsdaten (wenn ich jetzt richtig verstehe, was du damit vorhast) am sinnvollsten verwaltet, hängt natürlich von der Applikation ab. Meiner Erfahrung nach - aber die ist nicht allumfassend - macht es wenig Sinn, die Konfigurationsdatei in verwursteter Form aber praktisch unverändert vorzuhalten; habe ich beispielsweise eine Einstellung "calculation_backend = foo", halte ich mir nicht "foo" in einem String vor, um später die damit verbundene Funktion rauszukramen, sondern setze gleich beim Einlesen der Konfiguration einen entsprechenden Funktionszeiger (oder wie auch immer ich das gerade verwalte). Jedenfalls unternehme ich alles sinnvoll mögliche, damit sich kein Stück Client-Code damit rumschlagen muss, den Typ einer Variable zu erraten. Selbst, wenn die Einstellungen zur Laufzeit änderbar sein sollen, macht ein Event-basierter Ansatz da für mein Verständnis mehr Sinn - bring es gleich in eine vernünftig maschinenlesbare Form, und reich die Verarbeitung von Einstellungen nicht nach außen an Code weiter, der sich mit anderen Problemen rumschlagen will.
Ausnahmen gibt es im Wesentlichen da, wo man sich mit...naja, es läuft in gewisser Weise auf Laufzeitprogrammiersprachen raus. Nicht notwendigerweise so hochgestochen wie Script-Engines; ich denke da beispielsweise an Tabellenkalkulationen. Ich fluche zwar jedes mal über XLOPER, wenn ich an einem Excel-Plugin schreibe, aber ich sehe da tatsächlich keine sinnvolle Möglichkeit, das anders zu machen.
-
Nexus schrieb:
ccquestions schrieb:
Hab ich da nicht, sobald irgendwo mal der typeid header auftaucht im ganzen Programm das Runtime Type Tracing drin, was ja dann völlig idiotisch wäre, da ichs ja wirklich nur an der Stelle da benötige, notabene mit Typen, die bei der Kompilierung ja eigentlich dem Compiler sogar bekannt sind?
Runtime type information. Das ist ohnehin standardmässig aktiviert (wegen Exceptions) und normalerweise kein Problem.
Ist das wirklich so? Zumindest beim MSVC kann man Exceptions und RTTI getrennt von einander ein- und ausschalten. Mir ist auch nicht klar, was das Eine genau mit dem Anderen zu tun haben soll.
-
Tachyon schrieb:
...Exceptions und RTTI ... Mir ist auch nicht klar, was das Eine genau mit dem Anderen zu tun haben soll.
Naja wie soll denn die Runtime checken ob ein Catch-Handler passt, wenn sie über die Typen nichts mehr weiss?
-
hustbaer schrieb:
Tachyon schrieb:
...Exceptions und RTTI ... Mir ist auch nicht klar, was das Eine genau mit dem Anderen zu tun haben soll.
Naja wie soll denn die Runtime checken ob ein Catch-Handler passt, wenn sie über die Typen nichts mehr weiss?
Typischerweise schmeisst man keine Basisklasse und fängt dann abgeleitete Klassen. Und um von einer abgeleiteten Klasse auf die Basis zu kommen, braucht es kein RTTI. Die Basis einer abgeleiteten Klasse ist zur Compilzeit bekannt.
-
Wo ist eigentlich das Problem, eine (abstrakte) Basisklasse zu konstruieren, die nichts kann und von der alle weiteren abgeleitet sind.
Dann kann man mit dynamic_cast einfach voll in die Basisklassen-Kiste reingreifen und wenn der cast funzt, hat man einen Treffer gelandet und sonst halt nicht.
-
Caster_Master schrieb:
Wo ist eigentlich das Problem, eine (abstrakte) Basisklasse zu konstruieren, die nichts kann und von der alle weiteren abgeleitet sind.
Dann kann man mit dynamic_cast einfach voll in die Basisklassen-Kiste reingreifen und wenn der cast funzt, hat man einen Treffer gelandet und sonst halt nicht.
Ein Problem ist das nicht, aber wo ist der Sinn davon?
-
Ich hab manchmal das Gefühl, dass Java eine Generation von Programmierern herangezüchtet hat, die Objektorientierung für eine Eigenschaft der Sprache hält, in der ein Programm geschrieben ist. Es ist das Modell, Jungs! Nur, dass grep -r class src/ ein oder mehrere Zeilen zurückgibt, macht ein Programm noch nicht objektorientiert.
OOP 101: Die Basisklasse enthält das Interface, das an Stellen benutzt werden soll, an denen sich der umgebende Code nicht mehr dafür interessiert, mit welcher konkreten Klasse er denn jetzt genau arbeitet. Ist dieses leer, kann kann der umgebende Code mit dem ihm übergebenen Objekt nichts machen, dementsprechend ist es nutzlos. Objektorientiert betrachtet ist "alles" nichts. Jetzt kann man natürlich Upcasts ohne Ende betreiben, und bei Übergangslösungen, die schnell gekittet werden müssen und später durch eine vernünftige Anpassung des Modells ersetzt werden, habe ich dafür auch Verständnis, aber man verlässt damit die Gefilde sauberer Objektorientierung. Wenn Client-Code eh rauskramen muss, womit er eigentlich gerade arbeitet, kannste genausogut einen void* und ein Flag durchreichen und ein großes Switch-Statement aufmachen.
-
seldon schrieb:
OOP 101: Die Basisklasse enthält das Interface, das an Stellen benutzt werden soll, an denen sich der umgebende Code nicht mehr dafür interessiert, mit welcher konkreten Klasse er denn jetzt genau arbeitet. Ist dieses leer, kann kann der umgebende Code mit dem ihm übergebenen Objekt nichts machen, dementsprechend ist es nutzlos. Objektorientiert betrachtet ist "alles" nichts. ...
Vorsicht! Jetzt kommen die Protype-OOP-Jungs und verhauen dich *gg*