Merge branch 'master' into makefiles
[prop.git] / notes / Persistence.txt
blobee951339562cc348c06301d0f7de75228ff326c9
1 On Persistence
2 --------------
4 Persistent objects can be written and later recovered from a stream in
5 its entirety in a serial manner.
7 The persistence protocol is implemented in class Pstream in the library.
8 User defined persistent types should be derived from the base class PObject.
9 Each object should redefine the following virtual methods:
11 From  <AD/persist/pobject.h>
13     class PObject {
14     public:
15         virtual TypeId       persist_type_id   () const;
16         virtual const char * persist_type_name () const;
17         virtual Pistream&    persist_read      (Pistream&);
18         virtual Postream&    persist_write     (Postream&) const; 
19 #   define DEFINE_PERSISTENT_CLASS(ID, NAME)                             \
20         virtual TypeId       persist_type_id   () const { return ID; }   \
21         virtual const char * persist_type_name () const { return NAME; } \
22         virtual Pistream&    persist_read      (Pistream&);              \
23         virtual Postream&    persist_write     (Postream&) const;      
24     };
26 Briefly, method 'persist_type_id()' identifies the type of each object.
27 The TypeId for each user object must be a positive integer and each
28 type must have an unique id.  The method 'persist_type_name()' returns
29 a string identifying the type.  
31 Method 'persist_read()' and 'persist_write()' are invoked during
32 reading and writing from and to a persistence stream.  These methods
33 should not be called by the client directly but are instead called from
34 the persistent stream classes.  Being such it is alright to place
35 these virtual methods in the private section of the class. 
37 Currently, the persistent stream class heirarchy as structured as follows:
39          Pstream
40         /       \
41    Pistream    Postream
42         \       /
43         Piostream
45 Persistent input streams have the following protocol.  Notice that
46 an istream must be attached to the Pistream during object construction.
47 An optional memory manager can also be attached to the persistent stream,
48 and if this is supplied then all new storage will be acquired from this
49 memory manager instead of from the operator new.   
51 From  <AD/persist/pstream.h>
53    class Pistream : public virtual Pstream {
54    public:
55       Pistream(istream&);           
56       Pistream(istream&, Mem&);
57      ~Pistream();
58    
59       friend Pistream& operator >> (Pistream&, int&);
60       friend Pistream& operator >> (Pistream&, unsigned int&);
61       friend Pistream& operator >> (Pistream&, short&);
62       friend Pistream& operator >> (Pistream&, unsigned short&);
63       friend Pistream& operator >> (Pistream&, char&);
64       friend Pistream& operator >> (Pistream&, unsigned char&);
65       friend Pistream& operator >> (Pistream&, long&);
66       friend Pistream& operator >> (Pistream&, unsigned long&);
67       friend Pistream& operator >> (Pistream&, float&);
68       friend Pistream& operator >> (Pistream&, double&);
69       friend Pistream& operator >> (Pistream&, const char *&);
70       friend Pistream& operator >> (Pistream&, const unsigned char *&);
71       friend Pistream& operator >> (Pistream&, PObject&);
72       friend Pistream& operator >> (Pistream&, const PObject *&);
73    };
75 As such, the persistent streams do not actually perform the actual I/O;
76 it only implements the serialization and marshalling protocol.  This extra
77 layer of abstraction provides more flexibility with little performance
78 cost.  For example, we can attach any subclass of istream to a Pistream.
80 The serialization protocol performs the following functions:
81   (a) insert type information into the byte streams and decode the type
82       information on the way back.
83   (b) encode pointer sharing information so that the structure of a network
84       of objects is preserved on the receiving end.  And finally,
85   (c) encode the information in network byte order so that the output format
86       of an object is architecture independent and portable across platforms
87       (currently floating point numbers are not properly encoded.)
90 The output stream protocol is an exact mirror of the input protocol:
92    class Postream : public virtual Pstream {
93    public:
94       Postream(ostream&);           
95      ~Postream();
96    
97       friend Postream& operator << (Postream&, int);
98       friend Postream& operator << (Postream&, unsigned int);
99       friend Postream& operator << (Postream&, short);
100       friend Postream& operator << (Postream&, unsigned short);
101       friend Postream& operator << (Postream&, char);
102       friend Postream& operator << (Postream&, unsigned char);
103       friend Postream& operator << (Postream&, long);
104       friend Postream& operator << (Postream&, unsigned long);
105       friend Postream& operator << (Postream&, float);
106       friend Postream& operator << (Postream&, double);
107       friend Postream& operator << (Postream&, const char *);
108       friend Postream& operator << (Postream&, const unsigned char *);
109       friend Postream& operator << (Postream&, const PObject&);
110       friend Postream& operator << (Postream&, const PObject *);
111    };
113 Each type of persistent objects makes use of an 'object factory' to
114 create objects of each type.  Object factories are created and registered
115 using the template PObjectFactory: 
117 From  <AD/persist/pfactory.h>
119 template <class T>
120    class PObjectFactory {
121    public:
122       PObjectFactory(TypeId);
123       virtual PObject * create_object() { return new T; }
124    };
126 Here's an example: persistent classes Foo and Bar, where Bar is
127 a subclass of Foo, can be defined in the following manner:
129 In foo.h
131     class Foo : public PObject {
132        int    temp;        // temp is not a persistent member
134        int    integer;     // the rest are persistent
135        char * string;
136        short  array[10];
137        Foo  * next;
138        Foo  * last;
139     public:
140        DEFINE_PERSISTENT_CLASS(1, "Foo")
141     };
143     class Bar : public Foo {
144        Foo    foo;
145        char * name;
146     public:
147        DEFINE_PERSISTENT_CLASS(2, "Bar");
148     };
150 In foo.cc
152     //
153     // Create an object factory for each persistent type.
154     // The supplied type id for each factory must match that
155     // of the object.
156     //
157     PObjectFactory<Foo> foo_factory(1);
158     PObjectFactory<Bar> bar_factory(2);
160 Note the use of the macro DEFINE_PERSISTENT_CLASS to save work.
161 The class specific read and write methods are defined as follows:
163     Postream& Foo::persist_write(Postream& f) 
164     {  f << integer;
165        f << string;
166        for (int i = 0; i < 10; i++)
167           f << array[i];
168        f << next;    
169        f << last;
170     }
172     Pistream& Foo::persist_read(Pistream& f) 
173     {  f >> integer;
174        f >> string;
175        for (int i = 0; i < 10; i++)
176           f >> array[i];
177        f >> next;    
178        f >> last;
179     }
181     Postream& Bar::persist_write(Postream& f) 
182     {  Foo::persist_write(f);
183        f << foo;
184        f << name;
185     }
186     
187     Pistream& Bar::persist_read(Pistream& f) 
188     {  Foo::persist_read(f);
189        f >> foo;
190        f >> name;
191     }
193 Notice that all the persistent members must be read and written in
194 the same order.  The derivation of each persist_write() and persist_read()
195 methods is very similar to the trace() method for the garbage collector,
196 except that in this case we have to read and write all members, not just
197 pointers.
199 As we have mentioned, all sharing between pointers are preserved
200 in the persistent encoding, so it is safe to store general graph
201 like structures (i.e. even cyclic structures.)
203 While reading from a persistent stream, the storage for pointers to 
204 PObject's are allocated by calling the appropriate object factories
205 (e.g. next and last members in class Foo), which in turns calls the 
206 default new method for the class.  This, by default, is simply 
207 ::operator new().  It is up to the implementor for each class to override 
208 this method if other storage management strategy is needed.
210 One final note about strings (char * and unsigned char *): the storage
211 for a string is allocated on the fly during reading.  By default, this
212 storage is allocated by calling 'new char []'.  If instead a memory manager
213 is supplied to the Pistream's constructor, then memory will be allocated
214 from the memory manager.  All storage allocated are not kept track of in 
215 any way by the persistent streams, and it is up to the user to release
216 them when they are not longer needed.  
218 As a convenience, all garbage collectors are subclassed from GC, which in 
219 turns is subclassed from Mem, so it is possible to use garbage collection 
220 if it is difficult to manually manage the allocated storage.
222 Feb 19, 1995.
223 Allen Leung