Questions about TClonesArray::Remove / ::RemoveAt

I was looking into the code of TClonesArray::Remove and TClonesArray::RemoveAt in comparison to the TObjArray implementation. This was somewhat confusing to me. (I will come to my use case later on.)

  • Let’s call Remove with an object that is findable. TClonesArray returns the object that was “used to search for”. TObjArray returns the “found” object. These can be different, if the IsEqual member function has been overridden (which is the case for TObjString).
  • Let’s RemoveAt an object that exists. TObjArray returns the removed object. TCloneArray always returns nullptr.
  • Now let’s look at object lifecycle… TObjArray always returns objects in their current (usually fully contructed / etc) state. TClonesArray::Remove returns a pointer to a destructed object. So accessing anything there is probably UB. The pointer can only be used as a “Well, there was something” indicator. Like a bool.
  • Now let’s look at ownership. If TObjArray owns its contents (SetOwner), then Remove / RemoveAt can be used to “extract” / “release” (unique-ptr naming) the object and acquire ownership. For TClonesArray (which always owns its contents) this is not possible (see also TClonesArray not owner?)

TClonesArray derives from TObjArray. So one can use TClonesArray in place of a TObjArray. But Remove / RemoveAt have quite different semantics. This is at least surprising.

My use case is basically the last point / the point of the linked (quite old) thread. I would like to acquire ownership of one (or multtiple) objects from a TClonesArray. The only option that I have found by now: Have a separate TClonesArray, and use TClonesArray::AbsorbObjects to move the single object over. The separate TClonesArray will function as an owner for one single object than.


ROOT Version: latest


Rather than TClonesArray we have been recommending to use std::vector. What is leading to select TClonesArray for your code?

TL;DR – legacy, legacy, legacy…

Some libraries enforce it on us. Because we derive from some classes and we have to override member functions that use TClonesArray. And the other way round: We probide public APIs and can’t switch them just over without proper deprecations. All that sort.

TL;DR – legacy, legacy, legacy…

Well in that case … here you go :slight_smile:

TClonesArray returns the object that was “used to search for”. TObjArray returns the “found” object.

The difference is that TClonesArray::Remove runs the destructor on the found object and thus the used to search for, if it is different from the found object is actually still a valid object. However if it is the same, it returns an invalid object and this an UB. If we were still recommending and developing TClonesArray this would need to be fixed (probably by returning a nullptr like RemoveAt).

For TClonesArray (which always owns its contents)

For better or worse, this was indeed a fundamental design choice for TClonesArray

This is at least surprising.

Indeed but for the same reason you are here, we can’t really change that :frowning:

Have a separate TClonesArray, and use TClonesArray::AbsorbObjects to move the single object over. The separate TClonesArray will function as an owner for one single object than.

This is indeed the solution with the current code. Adding a ReleaseAt method should be “just” a matter about extracting the action done on the tc arguments of AbsorbObjects

Cheers,
Philippe.

1 Like