Thorsten Pawletta, University of Wismar, Dep.
of Mechanical-, Process- and E nvironmental Eng.,
Eine Klassenbibliothek zum persistenten Objektmanagement in C++
Autor : Jens- Uwe Dolinsky (andere Projekte)
e-mail : u.dolinsky@iname.com
Nähere Information über das Projekt können der folgenden Veröffentlichung entnommen werden:
Dolinsky J.-U. and Pawletta T.: A lightweight class library for extended persistent object management in C++, Software - Concepts & Tools, Volume 19 Issue 2 (1998) pp 71-79, Springer-Verlag Berlin Heidelberg.
C++ bietet vom Sprachstandard im Gegensatz zu anderen Programmiersprachen wie Smalltalk keine Lösung zum persistenten Speichern von Klasseninstanzen.
Diese Dokumentation stellt die Implementation und Funktionsweise eines allgemeinen, effizienten Persistenzmechanismus
für C++- Klassen dar. Realisiert wurde dieser durch eine generische Basisklasse "persistent", die
alle benötigten Datenstrukturen und Methoden kapselt. Durch Erben dieser Basisklasse können jeder beliebigen
Klasse diese Mechanismen zum Speichern/Laden von unterschiedlich typisierten Memberdaten verfügbar gemacht
werden. Diese Memberdaten können Inkarnationen skalarer oder strukturierter Datentypen sein. Dynamische Daten
können ebenfalls verwaltet werden, doch gibt es hierbei einige Besonderheiten zu beachten, dazu später
aber mehr. Es ist möglich, beliebig verkettete Strukturen (die auch Zyklen enthalten können) abzuspeichern
bzw. zu rekonstruieren.
2. Leistungsmerkmale der Klassenbibliothek
Die Realisierung besteht aus drei Klassen- und einer Strukturdefinition:
struct charlist_element { char *value; struct charlist_element *next; }; class charlist_typ { charlist_element *root; public: charlist_typ() { root = NULL;} ~charlist_typ(); void create(char* &s,unsigned l); void kill(char* s); };
Die Klasse charlist_typ implementiert eine einfach verkettete, lineare Liste von Zeigern auf dynamisch allokierte Speicherbereiche. Ein Listeneintrag enthält dabei einen Zeiger auf den allokierten Speicherbereich und einen Verweis auf den nächsten Listeneintrag. Die Klasse kapselt als einzige Membervariable einen Zeiger auf das erste Listenelement. Mit Aufruf der Methode create(s,l) wird eine l Bytes große dynamische Variable erzeugt und deren Adresse über den Parameter s zurückgegeben. Zusätzlich wird diese Adresse in der inhärenten Liste registriert.
Durch den Aufruf der Methode kill(s) wird der durch s referenzierte Speicherbereich nur dann freigegeben, sofern s in der Liste registriert wurde.
Bei Finalisierung einer charlist_typ- Instanz wird durch deren Destruktoraufruf die gesamte, sofern vorhandene Liste gelöscht. Dabei werden auch alle durch die Listenelemente referenzierten Speicherbereiche für die dynamischen Variablen freigegeben.
Diese Eigenschaft (Garbage Collection) wird in der Klasse persistent genutzt, welche eine Instanz der Klasse charlist_typ als Membervariable kapselt. Anhand dieser Liste erkennt der Persistenzmechanismus, ob dynamische Variablen durch ihn erzeugt wurden oder nicht. Das folgende Klassendefinition veranschaulicht das Problem:
class bsp { char* name bsp(char* s) {name = s;} };
Eine Instanz dieser Klasse initialisiert sein Element <name> mittels Konstruktoraufruf mit einem Zeiger auf einen String. Beim persistenten Speichern dieser Instanz wird dieser String mitgesichert. Vor dem Laden muß jedoch zuerst Speicher für den zu ladenden String reserviert werden. In der Konsequenz enthält die Instanz einen Zeiger auf eine lokale, dynamische Variable, deren Speicherplatz von der Instanz nicht verwaltet wird. Beim Finalisieren der Instanz entsteht dann ein Memoryleak. Das Problem tritt auch bei mehrfachen Reinitialisierungen auf. Würde eine Instanz mehrfach reinitialisiert werden, entstünden die Memoryleaks schon nach der zweiten Reinitialisierung. Diese Probleme lassen sich mit der implementierten Garbage- Collection vermeiden.
struct olist_element /*list element type of OBJECT_list*/ { int number; /*position number in OBJECT_list*/ persistent *object_ptr; /*pointer of persistentobj.*/ struct olist_element *next; /*pointer of next list element*/ }; class OBJECT_list /*list of read or written data*/ { olist_element *list_anchor; /*pointer of first list element*/ olist_element *current_position; /*pointer of last list element*/ int quantity; /*number of elements in list */ public: OBJECT_list(); ~OBJECT_list(); void insert(persistent*); /*insert element in list*/ int number_of(persistent*); /*get position number of element*/ persistent *object(int); /*get pointer of persistent obj.*/ void clear_list(); /*delete all list elements*/ };
Die Klasse OBJECT_list wird vom Persistenzmechanismus verwendet, um vor allem bei zyklischen, rückwärts verketteten oder ringförmigen Strukturen zu erkennen, welche Objekte schon geladen bzw. gespeichert worden sind. Sie ist Hilfsmittel des Objektmanagements der generischen Klasse .
Die Klasse ist ein einfach verketteter, linearer Listentyp, der Referenzen auf Instanzen eines von der Klasse persistent abgeleiteten Typs verwaltet. Ein Listeneintrag besteht dabei aus der Referenz auf das Objekt, einer Variable für die von der Liste zugeordneten Identifikationsnummer und einem Verweis auf den nächsten Listeneintrag. In der Memberdatenstruktur der Klasse befinden sich jeweils ein Zeiger auf das erste und letzte Listenelement sowie eine Variable für die Summe der Listeneinträge. Durch Aufruf der Methode insert(element) wird ein neuer Listeneintrag für element generiert, der dabei eine eindeutige Identifikationsnummer (ID) zugeordnet bekommt. Über die Methode object(number) kann man die Referenz des Objektes mit der Identifikationsnummer number erhalten. Ist das Objekt nicht in der Liste, wird ein Null- Zeiger zurückgeliefert. Mit der Methode number_of(object) kann die ID des durch object referenzierten Objekts erfragt werden. Findet sich für dieses Objekt kein Eintrag in der Liste, wird 0 zurückgegeben. Ist object ein Null- Zeiger, wird -1 geliefert. Initialisiert wird eine Instanz dieser Klasse mit einer leeren Liste.
3.3. Die Nachrichtenparameterstruktur
Alle Informationen, die Objekte für ihre persistenten Aktionen brauchen, sind in der Struktur persis_paramtyp zusammengefaßt. Am Anfang einer persistenten Aktion wird eine(!) Instanz dieser Struktur erzeugt, entsprechend des Lade- oder Speichervorgangs initialisiert und danach an alle betroffenen Objekte als Nachrichtenparameter weitergereicht.
Das Flag to_disk zeigt dem benachrichtigten Objekt an, was mit seinen Memberdaten geschehen soll. Das gesetzte Flag bezeichnet den Speichervorgang, das nicht gesetzte den Ladevorgang.
Das Element file repräsentiert die Datei für die persistenten Daten der Objekte. Beim Speichervorgang nimmt sie die Daten auf, beim Laden werden die Objekte aus den Dateidaten rekonstruiert.
Das Element current_olist ist eine Instanz der Listenklasse OBJECT_list (aus Abschnitt 3.2). Jedes von einer persistenten Aktion betroffene Objekt trägt seine Referenz in diese Liste ein (Stichwort: zyklische Strukturen) und kann durch sie auch Informationen über Objekte erfragen, die vom Persistenzmechanismus schon behandelt worden sind.
Die Struktur persis_paramtyp ist der einzige Schnittstellenparametertyp des gesamten Persistenzmechanismus der Basisklasse persistent (Abschnitt 3.4) für die nach außen transparente Kommunikation zwischen den Objekten.
Vor Verwendung muß die Instanz einer solchen Struktur initialisiert werden. Dabei wird durch Setzen des Flags to_disk die gewünschte Betriebsart (Laden bzw. Speichern) festgelegt und entsprechend eine Datei geöffnet (nach Betriebsart jeweils ausschließlich zum Lesen bzw. Schreiben), auf deren Dateisteuerblock das Element file verweist. Das Listenelement current_olist initialisiert sich bei der Inkarnation durch Konstruktoraufruf selbständig mit einer leeren Liste.
struct persis_paramtyp { BOOL to_disk; /* flag to sign if to read or to write*/ FILE *file; /* file descriptor */ OBJECT_list current_olist; /* while loading or storing it contains references to*/ /* the stored/loaded Objects*/ };
Um einer beliebigen Klasse persistente Fähigkeiten zu verleihen, muß man ihr bei diesem Entwurf zuerst die Eigenschaften der Klasse persistent vererben.
class persistent { /*private memberdata and -methods*/ private: charlist_typ dynamicstringlist; /*garbagecollector: to register all by persistent created strings*/ persis_paramtyp *persis_para; void store_instance(persistent*); /*stores the parameterinstance*/ persistent *get_instance(persistent*,BOOL); persistent *p_create_Instance(char *name); /*######## the only Communicationmethod to other persistent- derivates */ void p_message(persis_paramtyp*,char*); /*store obj. in file*/ public: int save (char *,char*,...); /* Interface- methods*/ int load (char *,char*,...); protected: persistent(){} persistent(persistent *){} /*special constructor only used by creating derivates by persistentmechanism*/ virtual ~persistent(){} DEF_GETCLASSNAME_FUNC(persistent) /*Macro: creates the p_get_classname()- method, this function will be created for derived classes implicitly by macro MOVE_DATA*/ virtual void move_data(){} /*capsulate data methods, for this generic class it's empty */ /*following methods are only for use in move_data - methods; dynamic_object(persistent**,persistent*,BOOL) macro, listed here only because of completeness */ void static_object(persistent &); /*for static persistent derivates*/ void static_string(char*); void data(void *,unsigned); /*for universal static datatypes; is used by several macros*/ void dynamic_string(char* &n,BOOL=true);/* for 0- terminated char* */ void dynamic_data(char* &,unsigned,BOOL=true); /*for dynamic data, notinstances of classes*/ void ptr_to_const(char *&,BOOL=true); void dynamic_obj(persistent **,persistent*, BOOL=true); void dynamic_template_obj(persistent **,persistent*,char*, BOOL=true); /*next function is for this generic class meanless derivated class overwrites this method implicitly by using the Macro MOVE_DATA*/ virtual void move_memberdata_of(char*) { PERSIS_EXCEPTION(ERR_UNKNOWN_CLASS);} };
Als zentrale Klasse enthält sie die Basismethoden und Datenstrukturen für den eigentlichen Persistenzmechanismus. Durch sie werden die Verfahren zur Behandlung verschiedener einfacher und komplexer Datentypen implementiert. Diese Mechanismen berücksichtigen sowohl statische als auch dynamische Membervariablen. Das Nutzerinterface der Klasse beschränkt sich nur auf die beiden Methoden load() und save(). Durch deren Aufruf wird der Lade- bzw. Speicherprozeß für die jeweilige Instanz angestoßen.
Diese beiden Methoden sorgen dafür, daß eine Datei für Ein- bzw. Ausgabe geöffnet sowie eine Instanz der Struktur persis_paramtyp erzeugt und entsprechend der Betriebsart (Laden oder Speichern) initialisiert wird. Anschließend senden sie diese Struktur als Parameter der Methode p_message() an das aufrufende Objekt. Dadurch wird dessen virtuelle Methode move_data() aufgerufen, welche die Implementationsschnittstelle zum Persistenzmechanismus der Klasse persistent ist.
In abgeleiteten Klassen wird diese Methode redefiniert, indem in ihrem Rumpf für alle Membervariablen die Aufrufe der jeweils zum Typ passenden Persistenzmethoden (data(...), static_data(...), usw.) aufgereiht werden.
Jede Persistenzmethode (Abschnitt 3.4.2) stellt bei ihrem Aufruf an der Membervariable persis_para die Betriebsart des Persistenzmechanismus fest, und führt die entsprechende Aktion (Laden bzw. Speichern) durch. Ist ein Memberelement selbst eine von persistent abgeleitete Klasseninstanz, so sendet die Persistenzmethode (z.B. static_object(...)) mittels Aufruf von p_message(...) eine Nachricht an das Objekt, wodurch in diesem der beschriebene Vorgang angestoßen wird.
3.4.1. Implementationsschnittstelle : die virtuelle Methode move_data()
Die Methode move_data() "beschreibt" dem Persistenzmechanismus die Memberdatenstruktur der jeweiligen persistent- abgeleitete Klasse. Enthält die Klasse keine für eine Wiederherstellung des Zustandes wichtigen Daten, ist der Funktionsrumpf leer oder es wird evtl. die move_data - Methode des Vorgängers aufgerufen.
Sonst enthält sie die Aufrufe der Persistenzmethoden (Abschnitt 3.4.2) für alle wichtigen Memberdaten, wobei die Reihenfolge keine Rolle spielt. Befindet man sich schon höher in der Ableitungshierarchie, so darf man auch die Datenfelder der geerbten Klassen nicht vergessen. Es muß also die move_data()- Methode des Vorgängers explizit aufgerufen werden. Der Aufruf kann an beliebiger Stelle, auch zwischen Aufrufen der Persistenzmethoden, erfolgen.
ACHTUNG: Der Kopf der move_data()- Methode wird implizit durch das Macro MOVE_DATA (siehe dort) erzeugt.
3.4.2. Persistenzmethoden
Die Persistenzmethoden werden von der Klasse persistent für unterschiedlich typisierte, dynamische oder statische Memberdaten bereitgestellt. Sie senden den Daten im objektorientierten Sinne Nachrichten, die sie je nach Zustand des Flags persis_para->to_disk auffordern, sich mit Hilfe der Datei persis_para->file zu Laden bzw. zu Speichern.
3.4.3. Globale Objektgenerierung
Um bei der Rekonstruktion Instanzen aller im Projekt verwendeten, von persistent abgeleiteten Klassen erzeugen zu können, müssen diese Klassen dem Persistenzmechanismus bekanntgemacht werden. Dies geschieht durch eine Methode, die nach Kenntnis aller im jew. Projekt verwendeten Klassen definiert werden muß (Wenn dynamische Strukturen verwendet werden).
Diese Methode ist wie folgt definiert:
persistent* persistent::p_create_Instance(char *name)
Sie erzeugt eine Instanz der Klasse, deren Name mittels Parameter name als String übergeben wird.
Beispiel: In einem Projekt wurden 3 Persistent- Derivate(Klasse1, Klasse2, Klasse3) definiert. Dann muß die p_create_Instance()- Methode wie folgt definiert werden :
persistent *persistent::p_create_Instance(char *name) { if (strcmp(name,"Klasse1")==0) return new Klasse1((persistent*)NULL);else if (strcmp(name,"Klasse2")==0) return new Klasse2((persistent*)NULL);else if (strcmp(name,"Klasse3")==0) return new Klasse3((persistent*)NULL);else return NULL; }
Weil diese Definition sehr aufwendig sein kann, wurde hierfür Makros definiert (siehe auch unten). Mit deren Nutzung sieht die Methodendefinition wie folgt aus:
DEF_CREATE_INSTANCE(REGISTER(Klasse1) REGISTER(Klasse2) REGISTER(Klasse3))
3.4.4. Symbolische Konstanten und Macros
Zur Vereinfachung der Anwendung und besseren Lesbarkeit der Quelltexte wurden verschiedene symbolische Konstanten
und Makros definiert.
Die folgende Liste gibt einen Überblick darüber:
Flagwerte für die Persistenzmethoden 4.,5.,7. und 8.
Zur Vereinfachungen der Persistenzmethodenaufrufe
wurden verschiedene Macros definiert:
Makros für die globale Objektgenerierung
DEF_CREATE_INSTANCE(parameters) persistent *persistent:: p_create_Instance(char *name)\ { parameters\ return NULL;\ }
Erzeugt den Kopf und den Rumpf der Methode
REGISTER(typ) if (strcmp(name,#typ)==0) return new\ typ((persistent*)NULL); else
Zentraler Makro für die Klassenanpassung
Der wohl wichtigste Macro MOVE_DATA(classname,predecessor) ist für jeden Persistenterben zu definieren.
Er hat 2 Argumente: classname ist der Klassennamen der Klasse, für die die Methode definiert werden soll; predecessor ist der Klassename der Klasse, von die aktuelle Klasse abgeleitet werden soll.
Der Macro erzeugt eine speziellen Konstruktor, der nur vom Persistenzmechanismus genutzt wird, die p_get_classname()- Methode (implizit durch Macro DEF_GETCLASSNAME_FUNC). Weiterhin erzeugt der Macro eine virtuelle Methode void move_memberdata_of(char* name). Mit dieser Methode wird es möglich, nur die Memberdaten einer geerbten Klasse zu behandeln. Es wird der Argumentstring mit dem aktuellen Klassennamen verglichen. Stimmen beide überein, wird die entsprechende move_data()- Methode aufgerufen, anderenfalls wird die Vorgängermethode aufgerufen und der Vergleich beginnt erneut. Abschließend generiert der MOVE_DATA- Macro den Funktionskopf der move_data()- Methode. Der Methodenrumpf für die move_data()- Methode kann also gleich angefügt werden (siehe Beispiel).
Dieser Macro soll dem Nutzer ebenfalls die formale Arbeit abnehmen und den Quelltext besser lesbar machen.
MOVE_DATA(classname,predecessor) classname(persistent* p):predecessor(p){}\ DEF_GETCLASSNAME_FUNC(classname)\ virtual void move_memberdata_of(char* name)\ { if (strcmp(classname::p_get_classname(),name)==0)\ classname::move_data();\ else predecessor::move_memberdata_of(name);\ }\ virtual void move_data()
Beispiel:
class klasse1: public persistent { public: int member1; float result; //zwei willkürliche Memberobjekte MOVE_DATA(klasse1,persistent) { persistent::move_data() //Vorgaengerdaten behandeln static_data(member1); static_data(result); } };
wird vom Präprozessor übersetzt in:
class klasse1: public persistent { public: int member1; float result; //Durch Macro erzeugt klasse1(persistent* p):persistent(p){} //spezieller Konstruktor virtual char* p_get_classname() {return "klasse1";} //liefert Klassennamen virtual void move_memberdata_of(char* name) { if (strcmp(klasse1::get_classname(),name)==0) klasse1::move_data(); //wenn name==Klassenname dann die //klassenspezielle Methode klasse1::move_data() else persistent::move_memberdata_of(name); //sonst dasselbe nochmal mit der Vorgaengerklasse } virtual void move_data() //eigentliche move_data()-Definition { persistent::move_data() //Vorgaengerdaten behandeln data((void*)&member1,sizeof(member1)); //Ersetzungen siehe oben data((void*)&result ,sizeof(result)); } };
3.4.5. Die beiden Schnittstellenmethoden load() und store()
Ausgelöst wird der Persistenzmechanismus nur durch Aufruf die beiden Schnittstellenfunktionen load() für Laden aus der datei und store() zum Speichern. Beide habe die gleiche Parameterliste:
void persistent::save(char *name,char *classname,...) void persistent::load(char *name,char *classname,...) // open File for readin
Alle anderen Funktionen können und dürfen von außen nicht aufgerufen werden.
Rückgabewerte der Interface- Methoden
Die Rückgabewerte sind Ergebnisse der auf dem Exception- Handling basierenden Fehlerbehandlung. Wird die Basisklasse ohne die symbolische Konstante WITH_EXCEPTIONS kompiliert, ist die Fehlerbehandlung deaktiviert. Das heißt, daß auch fehlerhafte Aktionen des Persistenzmechanismus keinen darauf hinweisenden Rückgabewert erzeugen. Schlimmstenfalls kann das Programm abstürzen.
struct tstruct { float b; char sname[10]; }; class test:public persistent { public : //der Einfachheit wegen ist alles public int intvec[10]; struct tstruct *ptstuct; char name[20]; long ldat; test *neighbour; char *constname; test() { constname="constant name"} MOVE_DATA(test,persistent) { static_field(intvec); //statisches Feld dynamic_data((char*)ptstruct,sizeof(tstruct),LOAD_AFTER_CREATING); //Zeiger muß laut Definition auf char* gecasted werden. //Beim Laden wird Speicherplatz für die Variable erzeugt static_string(name);//alternativ wäre static_field möglich, //weil name ein statisches Feld ist static_data(ldat); dynamic_object(neighbour,LOAD_AFTER_CREATING); ptr_to_const(constname);//costname wird durch Zeiger auf eine Stringkonstante initialisiert } };
Beim Speichern:
Die Instanz wird aufgefordert, ihre Datenfelder in Datei "testdatei.persistent" zu speichern.
1. CALL INSTANZ.save("testdatei.persistent") 2. ->Öffnen der Datei 3. INSTANZ.para.file = Dateihandle 4. INSTANZ.para.to_disk- Flag == Schreibmodus 5. CALL INSTANZ.p_message- Methode 6. IF (Instanz schon gespeichert) THEN 7. schreibe Objektnummer 8. RETURN 9. END IF 10. -> Eintragen der Adr. von INSTANZ in object_list- Liste 11. CALL INSTANZ.move_data- Methode 12. FOR (alle persistent- Derivate) 13. Schreiben des Klassennamen 14. CALL INSTANZ.derivat.p_message(INSTANZ.para) 15. ENDFOR 16. FOR (alle sonstige Memberdaten) 17. CALL (zugehörige Persistenzmethode) 18. ENDFOR 19. Datei file schliessen;
Beim Laden:
Die Instanz wird aufgefordert, ihre Datenfelder aus Datei "testdatei.persistent" zu reinitialisieren.
1. CALL INSTANZ.load("testdatei.persistent") 2. ->Öffnen der Datei 3. INSTANZ.para.file = Dateihandle 4. INSTANZ.para.to_disk- Flag == Lesemodus 5. CALL INSTANZ.p_message- Methode 6. IF (Instanz schon geladen) THEN 7. RETURN 8. END IF 9. -> Eintragen der Adresse von INSTANZ in Objektliste 10. CALL INSTANZ.move_data- Methode 11. FOR (alle persistent- Derivate) 12. -> Erzeugen von INSTANZ.derivat 13. CALL INSTANZ.derivat.p_message(INSTANZ.para) 14. ENDFOR 15. FOR (alle sonstige Memberdaten) 16. CALL (zugehörige Persistenzmethode) 17. ENDFOR 18. Datei file schliessen;
Um Klassen persistente Eigenschaften zu geben, muß bei der Implementierung folgendes beachtet werden:
DEF_CREATE_INSTANCE( REGISTER(Klasse1) REGISTER(Klasse2))
Nur einmal im gesamten Projekt
Beispiele:
#include "persis.h" // Die Klasse test1 hat außer Daten keine Referenzen // auf Objekte typedef class test1 : public persistent { char name[10]; int a,b; float c; struct { int f; char t;} struktur; MOVE_DATA(test1,persistent) { static_data(a); //Reihenfolge der Anweisungen beliebig static_data(b); static_data(c); static_data(struktur); static_field(name); } * * }; // Klasse test2 hat Daten und Referenzen auf Memberobjekte vom Typ test2 typedef class test2 : public test1 { int a,b; unsigned c; class test2 *nachfolger; MOVE_DATA(test2,test1) { static_data(a); static_data(b); static_data(c); test1::move_data(); // weil Erbe von test1 dynamic_object(nachfolger,LOAD_AFTER_CREATING); } * * };
Klasse test3 hat Referenzen auf Objekte vom Typ test2 und test1
typedef class test3 : public persistent { test1 *inst1,*inst2; test2 *inst3; MOVE_DATA(test3,persistent) { dynamic_object(inst1,LOAD_AFTER_CREATING); dynamic_object(inst2,LOAD_AFTER_CREATING); dynamic_object(inst3,LOAD_AFTER_CREATING); } * * }; DEF_CREATE_INSTANCE( REGISTER(test1) REGISTER(test2) REGISTER(test3)) //Implementation der p_create_Instance()- Methode
Zum eigentlichen Speichern muß nur die save(...)- Methode des zu des zu speichernden Objektes aufgerufen werden:
Beispiel:
h->save("pers_obj",NULL,para,NULL);
Die Instanz wird in die Datei "pers_obj" abgelegt (Erklärung zu para: siehe Behandlung von Unterbäumen). Vor dem Laden wird das Wurzelobjekt mittels new- Operator geladen. Danach wird die load(Dateiname)- Methode des zu ladenden Objektes aufgerufen:
Beispiel:
h = new test(); h->load("pers_obj",NULL,para,NULL);
Es ist auch möglich, nur die Memberdaten einer geerbten Klasse separat zu behandeln. Dabei muß in der save/load- Methode noch der jeweilige Klassenname angegeben werden.
h->save("pers_obj","test1",NULL);
Von der Instanz vom Typ test2 werden nur die Memberdaten des geerbten Typs test1 gesichert.
6. Behandlung von Unterbäumen und Strukturvariabilität
Die Interfacemethoden load(char*,char*,...) und save(char*,char*,...) haben als 3. Parameter eine persistent* - Argumentenliste variabler Länge. Wird zum Beispiel nur ein Unterbaum persistent gespeichert, muß dafür gesorgt werden, daß alle Verweise auf die jeweilige Parentclass nicht durch den Persistenzmechanismus verfolgt werden.
Dies wird erreicht, indem die entsprechenden Pointer auf die nicht zu speichernden Instanzen der Interfacemethode übergeben wird. Die Argumentenliste wird abgeschlossen mit dem NULL- Pointer (zwingend notwendig wegen der Ellipse !!!).
Diese Adressen werden in die Object_list der bereits gespeicherten bzw. geladenen Instanzen eingefügt, und deshalb von da an als gespeichert bzw. geladen betrachtet. Auf die Weise kann ein Unterbaum ,in deren Instanzen beliebige Referenzen auf andere Instanzen ,die nicht zu behandeln sind, existieren, selektiv gespeichert/geladen werden.
Wurde ein Unterbaum herausgespeichert, muß beim Einladen die Parameterliste der load()- Methode dieselbe Anzahl Parameter haben, damit der Baum in das bestehende System eingefügt werden kann. Werden der Interfacemethode beim Laden der Instanz weniger Parameter übergeben als beim Speichern, werden die fehlenden Referenzen mit NULL initialisiert. Auf diese Weise ist es möglich, aus Strukturen herausgespeicherte Objekte autonom zu laden.
(siehe auch example2.cc)
7. Ausblick und Hinweis
Weil die Standarddatentypen auf unterschiedlichen Rechnern verschiedene Größen oder Formate haben können, ist der Austausch der von Persistent erzeugten Dateien unter diesen Plattformen nur dann sinnvoll, wenn sie vorher in ein geeignetes Zwischenformat konvertiert wurden. Das heißt, die data()- Funktionen müssen so erweitert werden, daß sie z.B. vor dem eigentlichen Speichern die Werte in vereinbarte Zwischenformate z.B. Strings konvertieren und dann erst ablegen.