?
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