How to Write Objects to a File


Objects derived directly or indirectly from the class TObject can be made persistent (i.e. written to a file). Before writing an object to a file, you must first create a new file or open an existing file in UPDATE or RECREATE mode. For example,

Let's assume obj inherits from TObject. Objects in a file are identified by a key (see class TKey). A key is itself an object with all the necessary information to locate an user object in a file (its name, a title, size, position and a few other parameters). A file has a directory consisting of the list of keys. A key is automatically created by an operation such as obj->Write("keyname"). If the file compression attribute is set (TFile::SetCompressionLevel), the buffer holding an object is compressed before being written to the file. Write is a member function of the TObject class (TObject::Write) and perform the following operations:

The Streamer function is responsible for scanning the object data structure and copy the data to the buffer. This function may be user-written, but is, in general, automatically generated by the rootcint dictionary generator. To illustrate how Streamer is working, we will take the case of the ROOT class TShape. A TShape object describes a detector geometry basic element. TShape derives from TNamed that derives from TObject. It also derives from two ROOT attribute classes TAttLine and TAttFill. The TShape object has a few data members that are basic types and also a pointer to a material object. Streamer includes the code to read and write an object. We concentrate for the time being only on the writing part.

//_______________________________________________________________________
void TShape::Streamer(TBuffer &b)
{
   // Stream a TShape object

   if (b.IsReading()) {
      Version_t v = b.ReadVersion();
      TNamed::Streamer(b);
      TAttLine::Streamer(b);
      TAttFill::Streamer(b);
      b >> fNumber;
      b >> fVisibility;
      b >> fMaterial;
   } else {
      b.WriteVersion(TShape::IsA());
      TNamed::Streamer(b);
      TAttLine::Streamer(b);
      TAttFill::Streamer(b);
      b << fNumber;
      b << fVisibility;
      b << fMaterial;
   }
}

A statement like b << fNumber means: encode data member with name fNumber into buffer b. The TBuffer class defines the operators << and >> for all basic data types, but also for pointers to objects. If fList is a pointer to a list of objects, then the Streamer function for all the objects in the list is automatically called. The code corresponding to b << fObj is inlined for efficiency.

Let's now see what b << fMaterial is doing. fMaterial is a pointer to the material object. However, many shapes may reference the same material and we may want to write in the same buffer the complete list of all shapes, still writing only one copy of the referenced material. This is done automatically. TBuffer keeps a table of pointers to the objects already written in this buffer. When a reference to an already saved object is encountered, only the serial number of the object in the table is written, not the object itself.

The statement TNamed::Streamer invokes the Streamer function of the TNamed class responsible for saving its own data members. TNamed, in turn, will call TObject::Streamer. Note that the first operation is to write the class version identifier of the TShape class. This consists of two bytes and corresponds to the Class version ID given in the ClassDef statement in the TShape include file. This version ID can be used in the reading part to take into account possible (and likely) changes in the class definition. Therefore ROOT has an overhead of two bytes per level of inheritance for each object. This is necessary to support a complete and safe schema evolution. A mechanism with one single identifier per object would not be sufficient.

Most of the time, the Streamer function generated by rootcint is appropriate. You will have to implement your own function in the following cases:

When your object derives from TNamed, you may omit the parameter keyname. For example, for a TShape object, one could write: obj->Write(). The key created in this case will get the name from the TNamed object. If you write a key with a name already existing in the file, a new cycle (a la VMS) is created. You can use TFile::ls or TFile::Map to see the list of all records in a file.


Rene Brun, Fons Rademakers
Last update 13/1/97 by FR