Dear ROOTers,
I have run into a problem / unclear understanding concering ROOT I/O which the reference documentation can sadly not solve.
Background (skip this part and jump to the problem for tl;dr if you are an expert):
Let me describe with an example:
[ul]
[li]In our experiment, we have a class containing “Hit” information. [/li]
[li]Derived from that “Hit” class, we have a “Cluster” class. [/li][/ul]
Both are TObject-derived, have a ClassDef / ClassImp, and are compiled with dictionary (rootcint / rootcling).
Both live in TClonesArray 's.
Naturally, the “Cluster” has a list of elements, which can be an arbitrary number of "Hit"s. To conserve space and allow to re-use this elements-array in e.g. a “Track” class (to store the Clusters, then), I used
as type for that elements-array.
As only the pointers to the Hits are in the elements-array, they are not owned by the Clusters - the Hits still live in their TClonesArray, and ROOT I/O keeps the pointers intact / deduplicates them on streaming, as far as I understood.
To not leak in the std::vector
during TClonesArray::Clear, I implemented:
void Cluster::Clear(Option_t *opt) {
elementsArray.clear();
std::vector<const TObject*>().swap(elementsArray);
}
to free any heap allocated storage inside the std::vector. This is working perfectly fine, valgrind shows no leaks. I am well aware that the std::vector comes at some cost over a fixed-type fixed-size array especially when splitting into branches, but I consider this a negligible overhead over the gains in flexibility.
Problem:
Finally, the problem - it seems the std::vector<TObject*> believes it owns the TObject’s the TObject* inside point at during branch-reading.
This is what the documentation of TTree::GetEntry says for “TObject*” members, it seems this also holds for vectors of TObject*, which I found very unintuitive (since it is just the pointers which are stored inside the vector), but valgrind hinted me in that direction.
Due to this, when reading back the TTree (splitlevel 1) with the Hit and Cluster objects inside, I get a crash after a few entries - since the Hits are both owned by the splitted TClonesArray of Hits, AND owned by the std::vector<TObject*> in the Cluster objects, so they are deleted twice via the TGenCollectionProxy::Clear.
I tried to use
but it does not help (i.e. the “->” seems not to propagate to the TObject* members inside the vector).
How can I solve this, i.e. remove object ownership of the TObject* from the vector?
Is there a different shallow TObject* collection I can use instead of a std::vector which is suitable as a member of objects inside a TClonesArray, and does not own the objects inside?
I do not want to use:
[ul]
[li]TObjArray (I tried, but: https://sft.its.cern.ch/jira/browse/ROOT-6811 ). [/li]
[li]TRefArray (too expensive, we had this before, and already use many UUID in other places, which eats a lot of UUID handling time which is not needed here). [/li]
[li]TList (too expensive, I do not need a doubly linked list). [/li][/ul]
I am glad for any help.
Cheers,
Oliver