Das Array-Reflection Problem

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: Wieviel 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 structs 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.


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!