KAHIBARO
Discord Login Register

4.3. ROOT Collections

Lists

ROOT provides several container classes to hold groups of objects in memory. These are called ROOT collections. The most common general purpose container is TList. You will often meet TList indirectly, because many ROOT classes use it internally to manage their own children, for example the list of primitives drawn on a canvas, or the list of keys in a file directory.

A TList is conceptually similar to a list in other languages. It stores pointers to TObject instances, keeps them in a defined order, and lets you add, remove, and iterate over them. A TList does not care about the specific derived class, so you can store histograms, graphs, functions, or other ROOT objects together. Since it stores TObject*, you access the real type by casting when you retrieve elements.

You typically create a list with the default constructor:

cpp
TList *myList = new TList();

To add objects, you use Add:

cpp
TH1F *h1 = new TH1F("h1", "Histogram 1", 100, 0, 10);
TH1F *h2 = new TH1F("h2", "Histogram 2", 100, 0, 10);
myList->Add(h1);
myList->Add(h2);

Lists can optionally take ownership of their elements. If a TList owns an object, it will delete it when the list is deleted or when the object is removed from the list. This is controlled with SetOwner:

cpp
myList->SetOwner(kTRUE);

With ownership enabled, you must not delete the objects manually, because the list will handle that. Without ownership, the list only stores pointers and you are responsible for deleting the contained objects yourself. Object ownership in collections is a frequent source of confusion, and it connects directly to the more general topic of ROOT object ownership that is discussed elsewhere in this course.

A TList can also be searched by object name using FindObject:

cpp
TH1F *hFound = (TH1F*) myList->FindObject("h2");

This is one of the reasons why meaningful and unique object names are important in ROOT.

Another closely related class is TObjArray, which also stores TObject* but has an array like structure. TObjArray is introduced in more detail in the next section, together with other array like containers.

The following table summarizes some common TList methods you will encounter:

MethodPurpose
Add(TObject *obj)Append an object to the list
Remove(TObject *obj)Remove a specific object
FindObject(name)Find by name or pointer
SetOwner(Bool_t)Enable or disable ownership of elements
GetSize()Number of objects stored
At(Int_t i)Access object by index (via TObjArray)

The indexing related methods come from the fact that TList internally uses a TObjArray like structure. In practice, you will usually iterate with an iterator instead of indexed access.

Arrays

ROOT provides several collection classes that behave like arrays, that is, they are index based rather than purely list based. The most important ones for general use are TObjArray and the numerical arrays TArrayF, TArrayD, TArrayI, and similar.

TObjArray is an array of pointers to TObject, very similar in spirit to TList, but with stronger emphasis on indexed access. You create it and add elements like this:

cpp
TObjArray *arr = new TObjArray();
TH1F *h1 = new TH1F("h1", "Histogram 1", 100, 0, 10);
TH1F *h2 = new TH1F("h2", "Histogram 2", 100, 0, 10);
arr->Add(h1);          // placed at first free slot
arr->AddAt(h2, 5);     // explicitly placed at index 5

You access elements by index with At:

cpp
TH1F *h = (TH1F*) arr->At(5);

As with TList, there is an ownership mechanism. You can call arr->SetOwner(kTRUE) to let the array manage the lifetime of its contents. Again, combining this with manual delete calls leads to double deletion, so be consistent.

The TArray* classes store raw values rather than pointers to TObject. For example, TArrayD is an array of Double_t values, and TArrayF is an array of Float_t. These classes are useful when you need a simple numerical container that integrates smoothly with ROOT I/O, for example to store vectors of numbers inside a TTree or a persistent class.

You create and use a TArrayD as follows:

cpp
TArrayD arrD(5);    // array of 5 doubles
arrD[0] = 1.0;
arrD[1] = 2.5;
arrD[2] = 3.3;
arrD[3] = 4.7;
arrD[4] = 5.9;
double x = arrD[2];

TArrayD and its relatives provide basic operations such as resizing and setting all elements to a given value. They do not store TObject instances and they do not participate in ROOT ownership rules in the same way as TList or TObjArray.

The table below contrasts the most common ROOT collection types that behave like lists or arrays:

ClassStoresAccess styleTypical use case
TListTObject*Iteration, searchHeterogeneous collections, order matters
TObjArrayTObject*Indexed, iterationFixed or sparse index based collections
TArrayDDouble_t valuesIndexedNumerical arrays that must be ROOT serializable
TArrayFFloat_t valuesIndexedLike TArrayD but single precision

Important rule: TList and TObjArray store pointers, not copies. If you delete an object that is still stored in the collection, the collection will contain a dangling pointer. If the collection owns its elements, do not delete them manually.

Iterating over ROOT objects

ROOT collections provide several ways to loop over their contents. You will often need to iterate over a TList, for example to perform the same style change on all histograms, or to draw every object stored in a file.

For TList and TObjArray, the classic ROOT pattern uses an explicit iterator:

cpp
TList *myList = new TList();
myList->Add(new TH1F("h1", "h1", 100, 0, 10));
myList->Add(new TH1F("h2", "h2", 100, 0, 10));
TIter next(myList);
TObject *obj = nullptr;
while ((obj = next())) {
    obj->Print();
}

TIter is a helper that knows how to step through a TCollection. Inside the loop, you usually cast the TObject* to the specific type you expect:

cpp
while ((obj = next())) {
    TH1 *h = dynamic_cast<TH1*>(obj);
    if (!h) continue;
    h->SetLineColor(kRed);
}

The use of dynamic_cast is a safe way to handle lists that may contain different derived classes. If you are certain that all elements are of the same type, a static cast is possible, but it is less safe if the list content ever changes.

Many ROOT collections also support indexed access, which you can combine with a traditional for loop. This is more common with TObjArray:

cpp
TObjArray *arr = new TObjArray();
arr->Add(new TH1F("h1", "h1", 100, 0, 10));
arr->Add(new TH1F("h2", "h2", 100, 0, 10));
Int_t n = arr->GetEntriesFast();
for (Int_t i = 0; i < n; ++i) {
    TObject *obj = arr->At(i);
    if (!obj) continue;
    TH1 *h = (TH1*) obj;
    h->SetLineWidth(2);
}

GetEntriesFast returns the number of entries without some of the overhead of GetEntries. For simple loops over arrays that are already filled, this is usually what you want.

In modern C++ with ROOT, you can also sometimes use range based for loops, but this is not consistently available for all ROOT collections and depends on the ROOT version and headers. For a beginner friendly and portable style inside ROOT macros, the iterator based pattern shown above is safer.

You will also meet iteration over special collections provided by other ROOT classes. For example, a TFile or TDirectory has a GetListOfKeys method that returns a TList of keys stored in the file, and you can iterate over that list to load all objects:

cpp
TFile f("myfile.root");
TList *keys = f.GetListOfKeys();
TIter nextKey(keys);
TObject *keyObj = nullptr;
while ((keyObj = nextKey())) {
    TKey *key = (TKey*) keyObj;
    TObject *obj = key->ReadObj();
    obj->Print();
}

This pattern, a TList or TObjArray obtained from some other object, a TIter, and a loop that processes each contained TObject, appears throughout ROOT based analysis code. Mastering it gives you a flexible way to work with collections of histograms, graphs, and many other ROOT objects.

Views: 7

Comments

Please login to add a comment.

Don't have an account? Register now!