Reflektion reflektieren...



  • Hallo Leute!

    Sagt mal, haltet ihr Reflektion für eine gangbare Technik, um Serialisierung, Laufzeit-Erzeugung von UI und Scripting umzusetzen, ohne das in den den "publizierten" Klassen einzeln händisch umsetzen zu müssen?
    Also wenn man nur gewisse Schnittstellen wie "integer, real, enumeration, array-of, id-lookup, ..." definiert, dafür dann Serialisierer, oder Controller, die das mit Widgets zusammenbringen usw.? Mit Adaptern könnte man das in den reflektierten Objekten ziemlich schmal halten (Zumindest in Bezug auf die dortige Zeilenanzahl) ohne dass der wirkliche Code in dieser Hinsicht angepasst werden müsste (Also bloß "Nachrüstung"). Allerdings würde es dann eben einen umfangreichen Satz von Daten-Schnittstellen, Controllern und Adaptern geben. Ich frage mich, ob das auf Dauer so unüberschaubar wird, dass dieser Ansatz von Anfang an zum Untergang verurteilt ist.

    Ich komme gerade darauf, weil ich mein kränkliches Property-System, das noch auf String-Konvertierungen aufbaute, mal vernünftig refaktorisieren wollte und dann ganz schnell vom hundertsten ins tausendste kam. Während ich so mit dem Gedanken rumgespielt habe, ist natürlich auch etwas an Code entstanden, so dass ich vom rein technischen Gesichtspunkt nun in der Lage wäre, das voll dynamisch auszuimplementieren. Aber irgendwie habe ich so den Eindruck, dass ich damit in den Abgrund segle. Einerseits meine ich, dass ich beim Programmieren von Serialisierung, UI und Scripting zu 95% immer wieder gleiches oder zumindest ähnliches mache, andererseits habe ich das Gefühl, dass es trotzdem nicht so einfach ist, wirklich alles auf eine überschaubare Anzahl von generischen Schnittstellen herunterzubrechen. Wenn ich dann für jedes publizierte Klasse einen eigenen Satz von Schnittstellen, Adaptern, Controllern usw. benötige, habe ich irgendwie ja auch nicht wirklich etwas gewonnen.

    Selbst in C# wird nicht wirklich Reflektion benutzt, um UI/Serialisierung umzusetzen, oder? Ich kenne mich da nicht aus, aber meine mich zu erinnern, dass der UI-Designer auch bloß Klassen-Erweiterungen in echtem Code erzeugt und da nicht irgendwas dynamisch gebunden wird. Weiß da jemand mehr, wie das in Sprachen gehandhabt wird, die im Gegensatz zu C++ auch mit ihren mitgelieferten Bibliotheken darauf "getrimmt" sind, möglichst "schmerzfrei" moderne UI-Anwendungen zu entwickeln? XML-Beschreibungssprachen scheinen ja in Mode gekommen zu sein, aber ich habe keinen Überblick darüber, wie die UI dort mit den Datenmodellen zusammengebracht wird.

    Mein Traum wäre es im Moment, dass ich in der Anwendung selber ihre eigene UI entwickeln/designen könnte. Ein eigenes Widget-System habe ich ja schonmal, und da es sich um eine System-in-a-System-Anwendung handelt, würde das eigentlich auch darüber hinaus großen praktischen Nutzen besitzen. Aber Traum und Realität haben ja leider nur selten etwas miteinander zu tun...

    Würde mich freuen, wenn sich da schon jemand von euch eine Meinung gebildet hat, oder von Fehlschlägen berichten kann oder ähnliches...

    Viele Grüße,
    decimad



  • Dieser Thread wurde von Moderator/in SeppJ aus dem Forum C++ (alle ISO-Standards) in das Forum Rund um die Programmierung verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.



  • decimad schrieb:

    Sagt mal, haltet ihr Reflektion für eine gangbare Technik, um Serialisierung, Laufzeit-Erzeugung von UI und Scripting umzusetzen

    Ja. Muss nicht ein mal wahnsinnig komplizierte GUI sein. Ich hab früher z.B. öfter das PropertyGrid (oder wie auch immer das heißt) in C# benutzt. Das war eine generische GUI, um Eigenschaften editieren zu können. Und wenn man wollte, konnte man für bestimmte Eigenschaften den Editor überschreiben.
    Wir machen das auch so ähnlich mit Qt. z.B. kann man bestimmte Objektstrukturen aus einer XML Datei laden, und wenn jemand an seinen Knoten zusätzliche Eigenschaften haben will, fügt er seinen Klassen einfach Properties hinzu und die werden aus Attributen gelesen. Die ganze Leselogik sind vielleicht 20-30 Zeilen Code. Oder wir benutzen auch sowas ähnliches wie ein Property Grid für Einstellungen usw.



  • decimad schrieb:

    Selbst in C# wird nicht wirklich Reflektion benutzt, um UI/Serialisierung umzusetzen, oder?

    Doch, Binding.



  • Hey Mechanics!

    Also was aus meinem werkeln im Moment rausspringen würde wäre folgender Zusammenhang:

    http://postimg.org/image/j1cq2rd01/34568e89/

    Wobei ein Binding-Iface eben auch ein mapping von id auf weitere bindings darstellen kann. Es gibt vorgefertigte integer, real, enumeration usw. -Ifaces, aber man kann halt auch immer ein eigenes definieren und dann bei den Registries Controller usw. hinterlegen, wenn man möchte. Zusätzlich können Ifaces um zusätzliche Funktionalität erweitert sein (data.supports<X>(), data.supports("integer"))... Für die eigentliche Reflektion habe ich dann auch wieder Template-Helper, sodass die hauptsächliche Arbeit darin besteht, gute (eben wiederverwendbare) Interfaces zu designen, die Adapter möglichst flexibel nutzbar zu machen, und eben Controller zu entwerfen, die die Widgets mit den Daten-Schnittstellen verkuppeln.

    Wenn man zur Laufzeit an ein Control binden möchte, würde das nach dem Motto "control.bind( obj.data_binding() )" funktionieren. Das Control würde dann für den Binding-Typ nachschauen, ob es einen speziellen Controller dafür gibt und ansonsten alle Ifaces abklappern und schauen ob es für eines einen gibt (Das kommt mir noch etwas komisch vor).

    Die Adapter sind dann hauptsächlich Template-Kryptographie und ein paar Makros zur Vereinfachung im Code.

    Habe ich irgendwas übersehen, was ich auf jeden Fall noch bedenken sollte?



  • decimad schrieb:

    Habe ich irgendwas übersehen, was ich auf jeden Fall noch bedenken sollte?

    Keine Ahnung, das ist immer schwer zu sagen. D.h., im Endeffekt hast du überhaupt kein richtiges Reflection, sondern willst es erstmal in C++ nachbilden? Also auch kein Qt?
    Was bei dir das Binding Iface und der Adapter machen ist mir nicht ganz klar. Sowas ähnliches haben wir im System auch, also ein "Mapping" für normale C++ Klassen, die ohne Qt oder Codegenerierung oder sonstiges so eine Art Reflection anbieten sollen. Das ist aber nicht invasiv und unabhängig von den Klassen. Es wird ein externes Mapping aufgebaut, wo man Strings auf "irgedwas" mappt, um an die Daten zu kommen, z.B. auf boost::function (oder std), die auf eine Memberfunktion zeigt. Dann gibt es lauter vorgefertige Blöcke, um z.B. auf Listen zuzugreifen. Und dann gibts Klassen, die mit diesen Mappings was anfangen können, z.B. um die Werte an die GUI zu binden.



  • Ganz genau, ich habe nichts zur Verfügung außer einem Satz von Hilfsklassen, die Interface-Reflektion ala Query/Enum-Interface über Klassenhierarchien ermöglichen. So etwas wollte ich dann in die Bindings implementieren, um an die Schnittstellen zu kommen, bzw. sie aufzulisten. Das heißt, den Teil habe ich soweit zur Verfügung. Im Prinzip könnte ich die Bindings auch on-the-fly generieren, aber das Problem ist dann halt, dass ich dann zunächst nichts greifbares habe, um Observer auf den Schnittstellen zu halten, also die Bindung "Objekt/Property/Fragment->Binding" wäre nicht vorhanden. Darum wollte ich jetzt diese Binding-Objekte, welche dann die Schnittstellen implementieren und als Adapter zum eigentlichen Objekt/Member/Methoden fungieren, als echte Datenmember pro Objekt ablegen. Eine Möglichkeit das zu umgehen wäre Observer-Listen statisch in den Binding-Objekten abzulegen (mit Zeiger auf das adaptierte Objekt). Andererseits müsste man die Listen pro Objekt sortieren, also auch nicht gänzlich unaufwändig... Speicher vs. Laufzeit halt.

    Im Moment würde das irgendwie so aussehen im Client-Code:

    class my_class : public reflected_t<my_class> {
    public:
        my_class()
            : something_binder(this), root_group(this)
        {}
    
        void set_something(int foo) {
             // ...
    
             // Observer benachrichtigen.
             something_binder.notify( &simple_observer::value_changed );
        }
    
        int get_something() const;
    
        binding get_data_binding() override {
            return root_group;
        }
    
        const_binding get_data_binding() const override {
            return root_group;
        }
    
    private:
        INTEGRAL_METHOD_BINDER(get_something, set_something) something_binder;
    
        static const char something_id[];
    
        // Käme natürlich auch in ein Makro...
        static_id_group< my_class,
             id_element< something_id, decltype(&my_class::something_binder), &my_class::something_binder >
        > root_group;
    };
    

    Das Binding-Iface ist so etwas wie "integer":

    class integer : public binder_base, public observable<simple_observer>
    {
        virtual void set( int value ) = 0;
        virtual int get() const = 0;
        virtual std::pair<int, int> limits() const = 0;
    
        // der Einfachheit halber hier type_info
        const type_info* type() const override {
            return typeid(integer);
        }
    
        const char* name() const override {
            return "integer";
        }
    };
    

    Ist jetzt aus den Fingern gesogen. Der Adapter mapped diese Schnittstelle auf "set_something" und "get_something" statisch, wie im Beispiel da oben. Aber wenn der Datentyp dort meinetwegen short wäre, würde er auch die casts machen. Ebenso könnte es auch eine Implementierung über std::function<> geben, wenn man irgendwie Parameter mitgeben muss. Um diese Belange kümmern sich die Adapter. Der "Binder" bringt die Binding-Schnittstelle "integer" mit dem Adapter zusammen. Im Prinzip sind das nur Trait-basierte Templates gerade, nach außen treten dann nur die Schnittstellen im Binding zutage.

    Von außen könnte man jetzt sagen:

    auto binding = my_obj.get_data_binding();
    
       if( binding.supports<id_group>() ) {
           auto* ptr = binding.as<id_group>();
    
           for( unsigned int i=0; i<ptr->num_elements(); ++i) {
                cout << ptr->id(i) << "\n";
    
                auto elem_binding = ptr->element(i);
                cout << elem_binding.name() << "\n";
    
                // entsprechend "edit_widget::register_controller( integer::static_type(), edit_integer_controller_factory )"
                auto edit_controller = edit_widget::lookup_controller( elem_binding );
           }
       }
    

    Natürlich würde ich wohl nur selten direkt den Typ im Code abfragen, sondern eben Listen mit Factories für bestimmte Typen vorsehen.



  • Auch mit Code find ich das noch etwas verwirrend ^^

    D.h., der Binder kümmert sich jeweils um einen Wert? Wo wird diese "integer" Klasse verwendet?

    Was mir nicht gefällt, ist dass die Klassen irgendwas über die Binding wissen müssen. Ich hätte das komplett extern gemacht. Und so wie ich das sehe, könntest du das auch machen. Es gibt doch eigentlich keinen Grund, warum my_class von reflected_t ableiten und irgendwas über bindings wissen müsste.



  • Hey Mechanics,

    ja wie ich ja sagte, im Prinzip könnte ich das schon extern machen, mit einem großen Problem: Wie tracke ich Veränderungen am Objekt, wenn dann nicht der komplette Zugriff nur noch über die Reflektion stattfindet? Wie würde die UI mitbekommen, dass sich ein Wert verändert hat, beispielsweise. Also irgendwie müssen die Objekte schon zu einem gewissen Grad wissen, dass dort eventuell jemand lauscht.

    Diese Tatsache mal außen vor bin ich aber gerade dabei, das mal probeweise "extern" aufzuziehen. Also mit Meta-Klassen, von denen man mit der Referenz auf ein "Objekt" Objektrefenzen erzeugen kann, die man dann durchbrausen kann, was noch mehr Subobjektreferenzen erzeugt usw. usf.. Also alles on-the-fly aus einer Beschreibung wie (im Moment):

    struct sample_struct : public up::reflecting_object_t<sample_struct> {
    	int get() const {
    		return 5;
    	}
    };
    
    up::class_t< sample_struct,
    	up::integer_getter_binder<sample_struct, &sample_struct::get >
    > sample_class;
    
    int test() {
    	sample_struct sample;
    
    	up::reflecting_object& obj = sample;
    
    	auto ref = sample_class.bind(obj);
    	std::cout << ref.obj_->num_members();
    	auto member_ref = ref.obj_->member_ref(0);
    	auto* the_class = member_ref.obj_->get_class();
    
    	return 4;
    
    }
    

    Ganz schöner Wust von Objekten, Klassen usw., da verliert man schnell den Überlick. Muss ich mich schon noch ein bissl reinfuchsen, bis ich das alles klar vor Augen habe.

    Was ich mich nur eben frage ist, wie ich das am besten mit Changed-Events usw. durchführe.



  • Was wir in der Arbeit geschrieben haben, schaut irgendwie so aus:

    Binding b;
    b.bindMember("value", &MyClass::value);
    b.bindMethod("count", &myClass::getCount);
    

    Weiß jetzt nicht, wie das alles genau heißt/aufgebaut ist, muss jedesmal nachschauen. Jedenfalls bekommen die "Bindingmember" String Namen. Damit kann man die Bindings z.B. benutzen, um XML/Json zu serialisieren. So unübersichtlich ist die Basisversion eigentlich nicht. Wird aber dadurch unübersichtlich, dass wir auch recht komplexe Konstrukte unterstützen wollten.
    z.B. sowas:

    struct MyStruct
    {
      std::map<std::string, ISomeBaseIface*> plugins;
    };
    

    Also z.B. eine Map, die Zeiger auf polymorphe Objekte hält, die jeweils unterschiedliche eigene Bindings haben können. Und sowas wollen wir auch serialisieren können. Dann gibts noch verschiedene Möglichkeiten, irgendwelches Verhalten anzupassen, z.B. in welchem Format jetzt ein Datum gespeichert wird, oder dass irgendwelche Member als Attribute im XML landen. Und da wir viel mit Qt arbeiten, gibts noch zusätzlich Unterstützung für QVariants und QObjects, was noch mehr Dynamik reinbringt. Ist dadurch insgesamt natürlich schon recht komplex geworden.
    Wir haben keinen Tracking Mechanismus gebraucht. Wüsste jetzt spontan nicht, wie man das am besten machen könnte, dürfte aber machbar sein. Ich hätte das wahrscheinlich am ehesten optional gemacht. Das wäre schon etwas, was eine Klasse unterstützen kann, dann muss sie halt 1-2 Methoden wie addObserver/removeObserver anbieten, dann kann das Binding über SFINAE feststellen, ob die Klasse observable ist. Die Klasse würde nur eine Methode mit einem string aufrufen, z.B. valueChanged("count"). Ist aber nur eine spontane Idee und natürlich Geschmackssache, wie man das macht, dein Stil könnte natürlich wieder ganz anders ausschauen.



  • Hey,

    Jau, die Klassen-Meta-Objekte sowie die Binder könnte ich in diesem "design" auch zur Laufzeit zusammenbasteln und dann eben Container statt konstante Arrays beim Lookup benutzen und ähnliches, die erste Implementierung der Schnittstellen mache ich hier aber erstmal mit Templates soweit es geht. Eigentlich bin ich der Meinung, dass man das aber auch gut mixen kann, in der Hierarchie. Wie Du schon sagst, wenn man im Objekt Listen von reflektierten Objekten hat, wäre auch ein hervorragendes Binding möglich ala "object_list"- oder "object_map"-Member usw. Ich will halt immer ausprobieren, was man so compile-zeit-konstant hinbekommt. Member-Ids kommen natürlich auch noch, aber das war jetzt die geringste Sorge 😉 Ich werd' vielleicht ab und zu mal Fortschritte posten, immer schön, wenn man von außen dann hört, was man besser machen könnte etc.

    Danke Dir soweit auf jeden Fall, ich denke ich bin auf einem gangbaren Weg und bin vor allem nicht mehr so sehr am Zweifeln ob das alles Sinn ergibt, hab hier ja auch ein recht einzigartiges Szenario.



  • Okay, also man öffnet damit glaube ich so ein bisschen die Büchse der Pandora oder wie soll ich's ausdrücken 😉

    Ich hab' jetzt mal alles komplett aus den reflektierten Klassen rausgenommen, das Reflektions-System läuft parallel dazu. Hier ein Beispiel: http://pastebin.com/iA7TahP3

    Für jede reflektierte Klasse gibt's ein Objekt abgeleitet vom Typ "class_". Es gibt mappings von type_info -> const class_, string -> const class_.
    Objekt-Referenzen sind praktisch Paare aus "const class_" und dazu passendem "void".

    Jetzt gibt's ein Problem. Erst einmal bin ich nicht daran interessiert integrale Typen direkt nach außen zu reflektieren, ich muss ja irgendwie die Menge der Typen reduzieren, ansonsten ist das System unbrauchbar. In dem Beispiel ist schon einmal die Menge der integralen Integer-Typen auf ein Interface "integer" runtergebrochen. Wie man in dem Beispiel auch sieht, braucht man dafür hinter den Vorhängen Proxy-Objekte (die werden von den adapter-Klassen erzeugt), die diese Schnittstellen implementieren und diese auf die konkreten Objekte umbiegen. Und das sorgt für ein Problem: Wenn man typeid auf die entstehenden Objekte ausführt, bekommt man natürlich das type_info für diese Proxy-Typen.
    Wenn man nun eine Referenz auf eine Basis-Schnittstelle hat (und sowas hat man ja oft) oder nur ein solches hat um eine Reflektions-Referenz zu erzeugen und möchte den konkreten Typ, braucht man aber aus offensichtlichen Gründen erst einmal eine Referenz auf den Meist-abgeleiteten Typ (nicht exakt, die Proxy-Typen kann man sich eigentlich sparen). Aber da man da wegen der Externität nicht vom System her drankommt, bleibt nur typeid. Das aber sorgt dafür, dass man für jeden Proxy-Typ auch eine class_ registrieren muss, weil typeid ja die Typ-Id der Proxy-Typen liefert. Wegen des Runterbrechens der konkreten Typen auf Schnittstellen gibt es aber für die Mehrzahl der reflektierten Member Proxy-Klassen, sodass ich auf einmal zur Laufzeit auch mit sehr vielen Proxy-Meta-Klassen-Objekten zu tun hätte, die für sonst nichts zu gebrauchen sind.
    Eine andere Möglichkeit wäre, dass man ab der Konstruktion der Objekte in irgendwelchen Factories oder aus der Reflektions-Deserialisierung immer eine Referenz auf die meistabgeleitete Subklasse eines Objekts mitführt (Minus Proxy). Dann ist das System zwar immernoch transparent für die reflektierten Klassen, aber dafür zieht sich dann Reflektionscode durch alle anderen Teile des Programms. Also als Beispiel:

    // Überall wo Objekte verwaltet werden:
    struct holder {
        // Normaler C++-Code über Schnittstellen
        std::unique_ptr< some_base_iface > some_ptr;
    
        // Reflektionskram
        // Referenz zum "Meist-abgeleiteten Typ" minus Proxy-Typ.
        up::mutable_object_ref some_ref;
    };
    
    std::vector< holder > my_objects;
    

    Das heißt man müsste überall die Referenz für die Meistabgeleitete Nicht-Proxy-Klasse mitschleifen.

    Ich glaube mir fehlen so ein bisschen die Worte um das kompakt auszudrücken. Ist verständlich was ich meine, bzw. übersehe ich da eine dritte Alternative, die ohne einen Wust aus Proxy-Metaklassen oder Eingriffe überall in den Code auskommt?

    Viele Grüße



  • Hrmmm, wenn ich so drüber nachdenke, dann ist das eigentlich kein realistisches Problem. Die Proxies treten ja nur auf, wenn ich Member aufliste, dort weiß ich aber die echten Typen und gebe Meta-Referenzen raus, kann also die richtigen Referenzen rausgeben, also die meistabgeleitete Klasse, die ich benötige. Anderswo entstehen die Teile aus der Deserialisierung oder in Factories... Die entstehenden Objekte haben dann sinnigerweise sowieso eine Meta-Klasse, also kann ich mit typeid hochcasten.
    Damit gibt's jetzt nur noch das Problem, dass ich das hochcasten zum Metaklassentyp von typeid eigentlich nur über eine Pfadsuche erledigen kann 🙂 Klingt ein bisschen frickelig...



  • decimad schrieb:

    Hrmmm, wenn ich so drüber nachdenke, dann ist das eigentlich kein realistisches Problem. Die Proxies treten ja nur auf, wenn ich Member aufliste, dort weiß ich aber die echten Typen und gebe Meta-Referenzen raus, kann also die richtigen Referenzen rausgeben, also die meistabgeleitete Klasse, die ich benötige.

    Ist nicht so einfach nachzuvollziehen, was du schreibst 😉
    Bin mir nicht sicher, ob du mit deiner letzten Aussage sowas berücksichtigst:

    std::vector<std::unique_ptr<some_base_iface>> objects;



  • Hey Mechanics,

    für std::vector< std::unique_ptr< > > (Ohne den IFace-Teil sozusagen), gibt's dann nen Proxy ala "list" oder wenn ich fleißig bin "random_access" oder irgendwie so, und some_base_iface ist dann ja wieder der Fall, in dem man eine Meta-Klasse für das konkrete Objekt zur Hand hat (lookup über typeid) oder halt nur Zugriff auf die Meta-Klasse des Interfaces bekommt. So stelle ich mir das gerade vor. Wenn ein Objekt serialisierbar sein soll, muss ich also eine Meta-Klasse dafür haben, oder könnte auch schauen, ob das Interface ein Serialisierungspart beinhaltet, das kann man ja zur Laufzeit überprüfen (Ableitungsgraph der Schnittstelle wäre zur Laufzeit ja vorhanden).



  • Gnrml. Ich hab' jetzt nach einer Unterbrechung wieder Zeit mich damit zu beschäftigen und immer wenn ich ne längere Pause einlege, sehe ich die Dinge hinterher anders.
    Angefangen hatte es ja mit Metainformationen für Klassen, aber irgendwie interessiert's doch eigentlich nicht, ob es ein Klassentyp ist, oder ein Funktionstyp oder was auch immer. Was zählt ist, was man mit den Objekten anstellen kann.
    Daher wäre mein Gedanke jetzt, das einfach nur noch "Typ" zu nennen, der Member haben kann und sozusagen in Schnittstellen bzw. Subtypen zerfallen kann. Also ein Objekt eines Typs zerfällt dann beispielsweise in eine Liste der Schnittstellen oder "Fragmente", die sie anbietet. Für Klassenobjekte wären das die sinnigerweise eine Auswahl der Basisklassen.

    Nach meinem derzeitigen Ansatz sieht eine Objektreferenz irgendwie so aus:

    template< typename Type >
    class object_ref {
       type* type_;   // Laufzeit Metatyp
       Type* object_;
       intrusive_ptr<refcounted> lifetime_; // Falls Proxies im Spiel sind oder die Lebenszeit an diese Referenz gebunden ist.
    };
    

    Objekte ohne Compile-Zeit-Typen werde dann einfach in object_ref<(const) void> gehalten.
    Im Prinzip könnte ich die Lebenszeit von Proxies auch über den type* Metatyp regeln, dann bräuchte man den dritten Zeiger in der Referenz nicht, aber das hätte zur Folge:
    - Für jeden Proxy-Typ bräuchte man einen Proxy-Metatyp
    - Wenn die Referenz die Lebenszeit bestimmt, bräuchte man einen Pointer-to-Metatyp und ähnliches
    - Der Metatyp-Zeiger wäre kein Vergleichskriterium mehr

    Ferner stellt sich noch die Frage ob man denn Referenzen auf ein Objekt wie Referenzen auf eine Schnittstelle bzw. ein "Typfragment" des Objekts behandeln sollte, oder ob das zwei getrennte Dinge sind. Also nach dem Motto:

    class fragment_ref {
        void* fragment_ptr;
        type* fragment_type;
    };
    
    void func( object_ref<void> obj )
    {
        // A) Für die Lebenszeit muss man die Objektreferenz behalten
        Iface* iface_ptr = obj.query<Iface>();
    
        auto ref = obj.fragment(); // Der Typ des Objekts selber sozusagen
    
        for( auto fragment : ref.fragments() ) {
           // Subtypen oder Schnittstellen, die vom Objekt unterstützt werden
        }
    
        // B) Lebenszeit auch an Schnittstellenreferenzen gebunden,
        //    wobei die sich wie Objektreferenzen verhalten
        //    Das funktioniert überhaupt nur, wegen des intrusive_ptr's, der
        //    sich transparent um die Zerstörung des konkreten Objekts/Proxies
        //    kümmert (ansonsten müsste man zur Zerstörung immer zur Laufzeit
        //    zum konkreten Objekttyp hochcasten -> "langsam", ginge aber auch).
        object_ref<Iface> iface_ref = cast<Iface>(obj);
    }
    

    Also sollte man Objekte und "Fähigkeiten" dieser Objekte getrennt behandeln, oder eher nicht? Irgendwie ist das ja ein Tradeoff zwischen dem was "richtig (tm)" ist, und dem, was für den Benutzer des Systems praktisch ist.
    Ein Vorteil des Trennung wäre, dass die Objektrefenz sozusagen eine Identität für das Objekt darstellt, insbesondere sind keine Upcasts notwendig, um von einer Typ-Referenz erstman auf die Objektreferenz zu kommen und von dort wieder runterzucasten, um an eine andere Typreferenz zu kommen.

    Außerdem: Im Moment haben Objekte Subtypen (Fragmente) und Member, ist eine Trennung zwischen Datenmember und Funktionsmember überhaupt sinnvoll? Letztlich sind das ja alles Objekte, die einen haben halt eine Datentyp-Schnittstelle, die anderen sind eben "Callable" usw.

    Also das technische Konstrukt funktioniert soweit (und ist bei allen Ansätzen ja ziemlich gleich), aber ich bin von keinem der Ansätze bisher so sehr überzeugt, dass ich ihn mit all seinen Folgen guten gewissense umsetzen kann, schließlich hat das ja Auswirkung darauf, wie all der Client-Code gestrickt ist.

    Habt ihr Meinungen oder Vorschläge, was ich mir da mal anschauen könnte?

    Viele Grüße


Anmelden zum Antworten