How to implement IO for class inheriting from TClonesArray?


ROOT Version: 5.34.38
Platform: linux
Compiler: gcc/clang


I a data analysis and simulation framework for Gamma-Ray Astronomy,
we have a class that inherits from TClonesArray just adding a new method.

However, this class is not nicely serializable to root files (e.g. the TBrowser does not show member fields of the objects in the array as opposed to when using the TClonesArray directly).

What is needed to implement I/O same as for the TClonesArray?

MClonesArray.h:

#ifndef MARS_MCLONESARRAY_H
#define MARS_MCLONESARRAY_H

#include "TClonesArray.h"

class MClonesArray : public TClonesArray
{
public:
	enum EStatusBits {
       kForgetBits     = BIT(0),   // Do not create branches for fBits, fUniqueID
       kNoSplit        = BIT(1),   // No splitting in TBranch
       kBypassStreamer = BIT(12),  // Class Streamer not called (default)
	};

    using TClonesArray::TClonesArray;

    TObject **FirstRef();

    void FastShrink(Int_t n);
    void FastRemove(Int_t idx1, Int_t idx2);
    void UncheckedSort();
    virtual ~MClonesArray() {}
    ClassDef(MClonesArray, 2) // A TClonesArray with optimized sorting and shrinking
};

#endif //MARS_MCLONESARRAY_H

MClonesArray.cpp

#include "TObject.h"
#include "TClonesArray.h"
#include "MClonesArray.h"
#include "TClass.h"

ClassImp(MClonesArray)

TObject** MClonesArray::FirstRef() { return fCont; }

// --------------------------------------------------------------------------
//
// This is an extremely optimized version of ExpandCreateFast. It only resets
// the marker for the last element (fLast) to n-1 and doesn't change anything
// else. This implicitly assumes that the stored objects don't allocate
// memory. It does not necessarily mean that the slots after fLast
// are empty (set to 0). This is what is assumed in the TClonesArray.
// We also don't call Changed() because it would reset Sorted. If the
// array was sorted before it is also sorted now. You MUST make sure
// that you only set n in a range for which valid entries have been
// created before (e.g. by ExpandCreateFast).
//
void MClonesArray::FastShrink(Int_t n)
{
	fLast = n - 1;
}

// --------------------------------------------------------------------------
//
// This is a optimized (faster) version of Delete which deletes consequtive
// entries from index idx1 to idx2 (both included) and calls their
// destructor. Note that there is no range checking done!
//
void MClonesArray::FastRemove(Int_t idx1, Int_t idx2)
{
	// Remove object at index idx.

	//if (!BoundsOk("RemoveAt", idx1)) return 0;
	//if (!BoundsOk("RemoveAt", idx2)) return 0;

	Long_t dtoronly = TObject::GetDtorOnly();

	idx1 -= fLowerBound;
	idx2 -= fLowerBound;

	for (TObject **obj=fCont+idx1; obj<=fCont+idx2; obj++)
	{
		if (!*obj)
			continue;

		if ((*obj)->TestBit(kNotDeleted)) {
			// Tell custom operator delete() not to delete space when
			// object fCont[i] is deleted. Only destructors are called
			// for this object.
			TObject::SetDtorOnly(*obj);
			delete *obj;
		}

		*obj = 0;
		// recalculate array size
	}
	TObject::SetDtorOnly((void*)dtoronly);

	if (idx1<=fLast && fLast<=idx2)
	{
		do {
			fLast--;
		} while (fLast >= 0 && fCont[fLast] == 0);
	}

	Changed();
}

// --------------------------------------------------------------------------
//
// This is an optimized version of Sort which doesn't check the
// IsSortable flag before. It only sorts the entries from 0
// to GetEntriesFast().
//
void MClonesArray::UncheckedSort()
{
	if (fSorted)
		return;

	const Int_t nentries = GetEntriesFast();
	if (nentries <= 0)
		return;

	QSort(GetObjectRef(First()), fKeep->GetObjectRef(fKeep->First()),
		  0, TMath::Min(nentries, kMaxInt-fLowerBound));

	fSorted = kTRUE;
}

// This seems not to be enough to make this serialize nicely into
// root files. I'm out of ideas.
void MClonesArray::Streamer(TBuffer &b)
{
	TClonesArray::Streamer(b);
}

And this entry in our linkdef to create the dictionary:

#pragma link C++ class MClonesArray-;

Though uproot I found out, that the MClonesArray writes an empty string fClonesName to the file.

Any help would be greatly appreciated!

I don’t think that’s something we offer. @pcanal is the authoritative source of info here.

Out of curiosity, why do you need to use TClonesArray? Wouldn’t a std:vector be the better solution? Please also consider aggregating the TClonesArray (have it as a member) instead of inheritance.

Cheers, Axel.

What exactly do you mean with “It’s not something we offer”?

I was under the impression, that anything can be serialized to root files and be understood by ROOT when implementing the Streamer correctly. Is that not the case?

To the question of why TClonesArray or inherit from TClonesArray I can only say that this is an historical codebase and I only want to make limited changes to that. So if there is a way to adapt the Streamer to make it work as TClonesArray works, that would be great. Else we probably just can’t nicely serialize this object and have to live with that.

Well - TClonesArray has a special serialization mechanism, so it’s not just “any C++ type” :slight_smile:

The serialization of TClonesArray into a TTree involves not just the Streamer() but a whole branch type, TBranchClones. And that why we’ll need @pcanal’s verdict whether that’s feasible or not.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.

Technically inheriting from TClonesArray is not supported.

The easiest way is to write a class that contains a TClonesArray and forward all the TClonesArray:

class MyTClonesArray {
   TClonesArray fContent;
   ....
    TObject *At(Int_t idx) { return fContent.At(idx);
    ...
};

However there is 2nd solution … by inheriting from TClonesArray in a way that fools the system is confusing the two. This accomplished by not adding a ClassDef to the declaration of MClonesArray.

The major drawback is that you then need to be careful to never have the I/O create the array for you, as it would create a TClonesArray instead of a MClonesArray.

Cheers,
Philippe.