ROOT I/O
Reading and writing
objects
presents OO with probably its most severe challenge. As explained in
Concepts: Objects & Classes
objects are both code and data; it breaks the model to separate the two but
that is just what happens when C++ objects are written to a file, only the
data gets written. Clearly an OO model requires that objects are responsible
for their own I/O and this seems to imply that one of the toughest problems
gets left to the designer of the object to solve!
In practice this is not the case, OO
frameworks provides ways of generating code to do this. In the case of ROOT,
TObject
has a Streamer
member function
which is used to perform I/O. Any class that
inherits
from TObject, must supply its own version of the Streamer function.
CINT, the
interactive C++ interpreter can generate the Streamer function for any class,
by processing its
header file
so long as a few simple rules are followed.
Note that the Streamer function only has to deal with its own
data members
and in particular if these members are themselves objects then their Streamer
functions are called to write them out. The same is true for the data members
of the classes from which the class in question inherits. Consequently the
Streamer function is very simple, with a single I/O statement for every data
member and
base class
regardless how complex these are.
Although the Streamer function code is generated automatically, it is made
available to the user who may want to change it for two reasons:-
- The object may, to optimise performance, hold redundant information. For
example a vertex object might hold its position in both cartesian and polar
coordinates. During write, only a non-redundant set need be written, and
then, during reading, the redundant information can be be rebuilt. CINT cannot
possible deduce this and so such optimisation has to be supported by the user.
- A class may evolve in time but be required to be backward compatible with
respect to data files i.e. new objects must read old data. Each object has a
version number which is the first item written to the file, and consequently
the first to be read back. This allows the user to test on version number and
take the appropriate action to support old data.
The Streamer function is responsible for the process of converting an object's
data into a stream of bytes and vice versa. It is passed a
TBasket
which holds the byte stream. The TBasket class
overloads
the
<<
and
>>
operators so that it can perform this conversion with all the basic data types
from which all objects' data are ultimately composed.
To output a object requires several more actions:-
- The association of the TBasket with a file.
- The output of the TBasket to the file.
- The output of control information to allow the object to be retrieved
later.
This is done with another member function: Write, which is also inherited from
TObject. The Write function is passed the name that will be used to retrieve
the object later, for example:-
MyObject->Write("MyObject_1");
The first point to note is that no file name is supplied, whatever is the
current directory in the current
TFile
will receive a copy of the object's data. So it is essential that a
TFile object is created before attempting to output objects.
A Tfile can have a set of
directories (somewhat similar of HBOOK files), which are TDirectory objects.
Each has a set of keys (TKey objects) that have the names of the objects
written to the directory. The TKeys also have information about the position
of the TBasket byte stream in the file. So sending an object its Write
message
results in the creation of a Tkey, and a TBasket, the invocation of the
Streamer function to fill the TBasket, and the output of the TBasket. The
TKey objects are written when the file is closed.
To do the inverse
there is a Read member function that can be used to read on object back. For
example:-
MyObject->Read("MyObject_1");
but this, of course, assumes that there is already an object and that it is
O.K. to replace its data with the contents from the file. A rather more
natural way is to send the Get message to the TFile object which will create
the object and fill it with the data from file. For example, suppose f is the
TFile object that holds the copy "MyObject_1", then to create the object, fill
its data from the file and establish a
pointer
to it:-
MyClass *MyObject = (MyClass *) f.Get("MyObject_1");
Besides sending the Write message to a object, there are two other ways to
output objects:-
- Send the TFile its Write message. This results in every object in memory
being written out. Each is asked its name (the GetName message) which is
used as its key. For objects that have no name the object's class name is
used (with a version number to ensure the key is unique).
- Associate the object with a TTree.
Sending the TTree its Fill and GetEvent messages results in the object being
written and read. For more details see
TTree.
In summary, the essential points are:-
- TObject has a:-
- Streamer function to convert object data to/from a byte stream
- Write function to create a key and a buffer and perform the I/O.
- Any object that supports I/O must inherit from TObject and provide its own
Streamer function.
- CINT can generate this Streamer function
automatically but the user can fine tune if needed.
- To write an object to the current TFile it is sent its Write message
supplying the name to be given to the copy of the object on the file.
- To read an object it can be send its Read message or the TFile sent its
Get message supplying the name used to write the object.
- For collections of objects that together make an "event" TTree based I/O
ensures that the I/O is synchronised for the collection.
For more information see:-
Go Back to the
The ROOT Crib Top Page
If you have any comments about this page please send them to
Nick West