All instances of ROOT classes inheriting from TObject can be written to a ROOT file. This can be achieved using the TObject::Write method. Let's see an example:
TFile outputFile ("outputFile.root","RECREATE");
TH1F h ("myHisto","Histogram Title",100, -2, 2);
h.Write();
What happens behind the scenes when calling the TH1F::Write method, in a nutshell, is the following:
The Streamer method is responsible for scanning the object data structure and serialize the data to the buffer. This function may be user-written, but is, in general, automatically created when generating a dictionary. To illustrate how a 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. Let's now have a look to 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 by ROOT. TBuffer keeps a table of pointers to the objects already written in the current 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 can be set by the user either in the code of the class or in the selection file used to obtain the class dictionary. In this case it 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.
In the large majority of cases, the Streamer method generated in the dictionary is appropriate. You can implement your own Streamer method if you need to perform special operations related to the serialisation of the object.
A small note: 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 is created. You can use TFile::ls or TFile::Map to see the list of all records in a file.
outputFile.ls()
In general, all instances of classes that have a dictionary can be written by ROOT in ROOT files, for example:
std::vector<int> v {1,2,3,4};
outputFile.WriteObjectAny(&v, "std::vector<int>","myVector");
outputFile.ls();
outputFile.Close();