I/O of custom classes
This page describes how to read and write C++ objects to/from ROOT files.
Before you read this page, make sure you have read ROOT files.
When storing data with ROOT, data contained in your C++ objects is written in a platform-independent format to files on disk. This is what we call the “ROOT file format”.
ROOT’s I/O system is tailored to the needs of the high-energy physics community. In particular, it has the following notable features:
- support for object-wise (row-wise) and column-wise I/O
- arbitrary C++ objects can be written/read without the need of user-defined I/O code
- automatic handling of changes in the class data members and their types over time (“schema evolution”)
- data is compressed and decompressed transparently and users have access to a number of different compression algorithms
- transparent remote I/O
- the technology is tuned for very large datasets
In order to store your C++ types, ROOT needs to know about its data members and their types. ROOT can extract that information from your header files with the help of its C++ interpreter, Cling, and store it in ROOT dictionaries. A dictionary (“reflection database”) contains information about the types and functions that are available in a library in the form of C++ code that can be linked into your application. These dictionaries can be generated automatically by ROOT in a few different ways, which we describe in the following section.
Note
Dictionaries are not required to use a given C++ type in the ROOT interpreter (e.g. in the ROOT prompt, via PyROOT or in Jupyter notebooks). They are only required to perform I/O of user-defined classes.
Generating dictionaries
A dictionary consists of a C++ source file, which contains the type information needed by Cling and ROOT’s I/O subsystem. This source file needs to be generated from the library’s headers and then compiled and linked to the application that needs to perform I/O of the included classes.
There are three ways to generate a dictionary:
- using ACLiC: use this method to generate class dictionaries for quick prototyping and interactive development.
- using
rootcling
: this is a low level command line tool to generate dictionaries. You can invokerootcling
e.g. from a Makefile or a shell script. - using CMake: use this method to integrate ROOT I/O in your C++ framework build system.
Using ACLiC
When you compile code from the ROOT prompt using ACLiC, ROOT automatically generates the dictionaries for the types defined in that code and compiles them into a shared library.
For a standalone source file MyClass.cxx
, we can interactively compile the source file into a library and, at the same time, create dictionaries for the types defined in it, with:
At this point, the ROOT interpreter has loaded all the information required to perform I/O of the types in MyClass.cxx
:
The library containing the compiled dictionary will be called MyClass_cxx.so
(and, by default, the generated dictionary source file is automatically deleted).
Extra metadata that ROOT uses to find back dictionaries at runtime is stored in files with extensions .d
and .pcm
.
If instead our code is available as a header file and a pre-compiled shared object, we can load them in the interpreter and create dictionaries from the header like so:
Using rootcling
You can manually create a dictionary by using rootcling
:
-
DictOutput.cxx
Specifies the output file that will contain the dictionary. It will be accompanied by a header fileDictOutput.h
. -
<OPTIONS>
are:-
-I<HEADER_DIRECTORY>
: Adding an include path, so thatrootcling
can find the files included inHeader1.h
,Header2.h
, etc. -
-D<SOMETHING>
: Define a preprocessor macro, which is sometimes needed to parse the header files.
-
-
Header1.h Header2.h...
: The header files. -
Linkdef.h
: Tellsrootcling
which classes should be added to the dictionary, → see Selecting dictionary entries: Linkdef.h.
Note
Dictionaries that are used within the same library must have unique names, even if they reside in separate directories.
Embedding the rootcling call into a GNU Makefile
We recommend usage of the CMake build system generator, but if you need to use a GNU Makefile, there is the following rule for generating a dictionary (see code snippet below). It will create a new source file, which you should
compile like all the other sources in your library. In addition, you need to
add the include path for ROOT, and you might have to link against ROOT’s libraries (we do so by means of root-config --libs
, which outputs the necessary compiler flags).
This rule generates the rootcling
dictionary for the headers $(HEADERS)
and a library
containing the dictionary and the compiled $(SOURCES)
:
Using CMake
For information on integrating ROOT into your CMake project, see this page.
ROOT also provides the ROOT_GENERATE_DICTIONARY
CMake
function to generate dictionaries as part of a CMake
project build. It is a convenient wrapper on top of the rootcling
command that we discussed above.
Files named ${dictionary}.cxx
and ${dictionary}.pcm
are created from the provided headers and the linkdef
file, calling the rootcling
command. See the following section for more details on the linkdef
file.
The MODULE
option is used to attach the dictionary to an existing CMake
target: the dictionary will inherit the library and header dependencies of the specified MODULE
target; CMake
will also link the generated dictionary to the target.
Here is a complete example usage:
Selecting dictionary entries: Linkdef.h
A “linkdef file” selects which types will be described by a dictionary generated by rootcling
.
The file name must end with Linkdef.h, LinkDef.h
, or linkdef.h
. For example, My_Linkdef.h
is correct, Linkdef_mine.h
is not.
Here is an example linkdef file:
The rootcling
directives are in the form of #pragma
statements.
The nestedclasses
directive tells rootcling
to also generate dictionaries for nested classes of selected outer classes, like in the following snippet:
The namespace directive instructs rootcling
to include every type in the selected namespace in the dictionary.
Note
The
+
after the class name enables extra features and performance improvements in the I/O of the type. Remember to always add it to your linkdef directives.
Note
In the past, linkdef files also contained directives for global variables, functions and enums: these directives are ignored since ROOT version 6.
Selection by file name
Sometimes it is desirable to create a dictionary for everything defined in a given header file.To that end, the following directive is available:
Make sure that path/to/MyHeader.h
corresponds to one of the header files that is passed to the rootcling
invocation.
Choosing between row-wise and columnar storage
ROOT data is very often stored inside TTree
objects (which are in turn stored inside ROOT files, often manipulated via the TFile
class), but it is also possible to store your custom types directly inside a TFile. To pick one or the other option, think of TFiles as directories and TTrees as databases or datasets: if you want to save a single object to a ROOT file, you can store it directly in the TFile (e.g. via TFile::WriteObjectAny); if you want to store several different values of a given type and later access all of those values as part of a single dataset/database, then it’s probably better to use a TTree.
For more information on ROOT files, see ROOT files.
For more information on TTree, see Trees.
The ClassDef
macro
The ClassDef
macro can be inserted in a class definition to add some reflection capabilities to it. It also attaches a “version number” to the class that can be used for schema evolution.
Having a ClassDef
is mandatory for classes inheriting from TObject
, otherwise it is an optional ROOT I/O performance optimization.
The syntax is:
The version number identifies this particular version of the class. A version number equal to 0 tells ROOT to not store the class in a root file, but only its base classes (if any).
ClassDef
injects some methods in the class definition useful for runtime reflection.
Here are the most important ones:
static const char *Class_Name()
: returns the class name as a C-stringstatic TClass *Class()
: returns aTClass
instance that can be used to query information about the classMAYBE_VIRTUAL TClass *IsA() const MAYBE_OVERRIDE
: same asClass()
, but it returns theTClass
corresponding to the concrete type in case of a pointer to a baseMAYBE_VIRTUAL void ShowMembers(TMemberInspector &insp) const MAYBE_OVERRIDE
: useful to query the list of members of a class at runtime (seeTMemberInspector
)
Use ClassDefOverride
to include the override
keyword in the appropriate injected methods.
Use ClassDefNV
to not mark any of the injected methods as virtual
.
Example
Note The class version number must be increased whenever the class layout changes, i.e. when the class data members are modified.
Restrictions on types ROOT I/O can handle
For ROOT to be able to store a class, it must have a public constructor.
ROOT currently does not support I/O of std::shared_ptr
, std::optional
, std::variant
and classes with data members of these types (unless they are marked as “transient”).
ROOT can store and retrieve data members of pointer type but not reference type.
Data members, and in particular pointer data members, must be initialized by the class constructor used by ROOT I/O (most typically, the default constructor):