Das Array-Reflection Problem
« | 14 Nov 2020 | »Da komme ich endlich zum Thema Laden und Schreiben von Konfigurationsdateien und die erste provisorische Implementierung steht, schon kommen mir Zweifel.
Im Anfang war die Kraft!
Doch, auch indem ich dieses niederschreibe,
Schon warnt mich was, daß ich dabei nicht bleibe.
Mir hilft der Geist! Auf einmal seh ich Rat
Und schreibe getrost: Im Anfang war die Tat!
Wie schön und einfach wäre die Welt, käme sie ohne Arrays aus. Jede XML, JSON oder YAML Datei könnte man mit Leichtigkeit in eine C-Struktur (struct) überführen, wenn diese mit ein paar Reflection-Infos versehen ist.
Array sind jedoch tückische Konstrukte. Sie stehen als eigenständige Objekte da, beinhalten aber eine beliebige Menge von anderen Daten.
… und hier stellt sich die Frage: Wie viel darf oder soll ein Array von seinem Inhalt wissen?
Arrays mit Konstruktionsbauplänen
Arrays sind gemäß ihrer Schnittstelle so gebaut, dass sie von außen fertige Daten überreicht bekommen. Diese “speichern” sie und sie auf Anfrage wieder zurück.
Aber so ein Array-Container-Objekt kann nicht selbständig Daten erzeugen, die es dann aufnehmen kann.
Über dieses Problem bin ich nun beim Thema Array-of-Struct
gestoßen.
Im GATE Projekt, haben alle primitiven Datentypen eine Type-ID
und jedes
Array eines primitiven Types hat ebenfalls eine solche ID.
Und dann gibt es dann noch spezielle structs
mit Deskriptoren, wodurch eine
zusammengesetzte Datenstruktur ganz am Anfang eine standardisierte
Inhaltsangabe mitbekommt und der Datentyp generisch auslesbar und
beschreibbar wird.
Ein solches gate_struct_t
ist quasi die Basisklasse für jede beliebige
struct
Implementierung mit unterschiedlichen Membern, jedoch alle starten
mit einem Reflection-Descriptor der diese Member beschreibt.
Jede Array-of-gate_struct_t
Instanz erhält somit die gleiche Typen-ID,
obwohl der Inhalt und Typ seiner struct
s ein anderer sein kann.
Wie soll jetzt aber ein Parser aus dem generischen Array-of-gate_struct_t
genau einen ganz spezielle struct
Instanz zusammenbauen, die dann in ein
Array eingefügt werden soll?
Erweiterung des Kopierkonstruktors
gate_arraylist_t
Objekte können Daten von außen in sich selbst
hineinkopieren (ganze analog zu std::vector
in C++ und das tun sie über
einen Copy-Constructor Pointer, der formal zum Typen der Array-Einträge
gehört. Aber der kann nur etwas kopieren was schon da ist und nichts
“Neues” erschaffen.
Ohne jetzt alles im gesamten Framework ändern zu müssen, blieb mir folgender Weg offen:
Die Copy-Constructor-Funktion soll im Falle eines Null-Pointers eine neue leere Instanz eines Objektes erschaffen.
Erzeugt man nun also eine Instanz von gate_arraylist_t
und initialisiert
diese mit dem Konstruktorpointer seines Inhalts, erlangt das entstehende
Array die Fähigkeit nicht nur bestehende Daten in sich hineinzukopieren,
sondern auch neue Daten per “Defaultkonstruktor” zu erzeugen.
Die neue Funktion gate_arraylist_create_item()
liefert ein neues freies
Element mit dem richtigen Typ, und wenn dies wiederum ein struct
mit
Deskriptor ist, kann wieder der generische Code anlaufen, um das Element
(z.B.: per Deserialisierung) zu befüllen.
Das funktioniert natürlich nur, weil ein struct
-Descriptor neben der
Type-ID auch die Summe der Größen aller Member-Datentypen beschreibt.
Am Ende wird das neue Element in das Array aufgenommen und alle sind glücklich.
Fazit
Hier zeigt sich wieder mal, welche Vorteile Sprachen mit eingebauter Reflection haben. Und während man sich in C++ noch mit Templates etwas behelfen kann, steht man in C ganz alleine da.
Der Missbrauch von C-Objekt-Kopierkonstruktoren gefällt mir zwar nicht sehr, doch es stellt eine Möglichkeit dar, zwei Fliegen mit eine Klappe zu schlagen.