Writing multiple-inherited object to TClonesArray

Problem is following. I have a class, lets call it TestClass with TClonesArray holding other class.

class TestClass : public TObject
{
    TClonesArray * data;
};

The other class is GoodTrackSim defined as follow

class BaseTrack : public TLorentzVector {};
class BaseTrackSim : public virtual BaseTrack {};
class GoodTrack: public virtual BaseTrack {};
class GoodTrackSim: public GoodTrack, public BaseTrackSim {};

Of course, each of classes above has ClassDef and ClassImp macros.

When creating the branch, the program gives following warining and seg faults with following stack track

error<TClonesArray::SetClass>: GoodTrackSim must inherit from TObject as the left most base class.
… some lines
error<TUnixSystem::DispatchSignals>: segmentation violation
… some lines here
#5 0x00007f57e677e7d4 in TClass::CallShowMembers(void*, TMemberInspector&, int) const () from $ROOTSYS/lib/libCore.so.5.34
#6 0x00007f57e678b2dd in TClass::BuildRealData(void*, bool) () from $ROOTSYS/lib/libCore.so.5.34
#7 0x00007f57e3e14d63 in TTree::BuildStreamerInfo(TClass*, void*, bool) () from $ROOTSYS/lib/libTree.so.5.34
#8 0x00007f57e3e5bd01 in TBranchElement::Unroll(char const*, TClass*, TClass*, char*, int, int, int) () from $ROOTSYS/lib/libTree.so.5.34
#9 0x00007f57e3e5e27a in TBranchElement::Init(TTree*, TBranch*, char const*, TStreamerInfo*, int, char*, int, int, int) () from $ROOTSYS/libTree.so.5.34
#10 0x00007f57e3e5eba2 in TBranchElement::TBranchElement(TBranch*, char const*, TStreamerInfo*, int, char*, int, int, int) () from $ROOTSYS/libTree.so.5.34
#11 0x00007f57e3e18e37 in TTree::BronchExec(char const*, char const*, void*, bool, int, int) () from $ROOTSYS/lib/libTree.so.5.34
#12 0x00007f57e3e0eac8 in TTree::Bronch(char const*, char const*, void*, int, int) () from $ROOTSYS/lib/libTree.so.5.34
#13 0x00007f57e7b22ed3 in Branch (splitlevel=99, bufsize=8000, addobj=0xa57e5e8, classname=<optimized out>, name=0x7fff4dce1650 “GoodTrackSim.”, this=0xa5c72b0) at $ROOTSYS/include/TTree.h:326

Question is, is there a chance that this construction with holding the final class will work or rather I must redesign the class hierarchy?

The example above works, when the object in TClonesArray is of BaseTrack, but fails already for BaseTrackSim and GoodTrack, so I guess it is due to virtual inheritance.

I prepared MWE to demonstrate this. Please copy code below to shell script and execute, it will prepare all sources, compile, you just need to run ./a.out. You can play in the constructor of TestClass and change object to BaseTrack and it will work.

#!/bin/bash

cat <<EOF > test_tree.h
#ifndef TESTTREE
#define TESTTREE

#include "TLorentzVector.h"
#include "TClonesArray.h"
#include "TTree.h"

#include <iostream>

using namespace std;

class BaseTrack : public TLorentzVector
{
public:
    BaseTrack() { cout << "BaseTrack" << endl; }
    ClassDef(BaseTrack,1); void print() {}
};

class BaseTrackSim : public virtual BaseTrack
{
public:
    BaseTrackSim() { cout << "BaseTrackSim" << endl; }
    ClassDef(BaseTrackSim,1); void print() {}
};

class GoodTrack: public virtual BaseTrack
{
public:
    GoodTrack() { cout << "GoodTrack" << endl; }
    ClassDef(GoodTrack,1); void print() {}
};

class GoodTrackSim: public GoodTrack, public BaseTrackSim
{
public:
    GoodTrackSim() { cout << "GoodTrackSim" << endl; }
    ClassDef(GoodTrackSim,1); void print() {}
};

class TestClass : public TObject
{
    public:
    TClonesArray * data;
    TestClass() : TObject() {
        data = new TClonesArray("GoodTrackSim", 10);
        if (data)
        {
            for (int i = 0; i < 4; ++i)
            {
                std::cout << i << std::endl;
                new (&((*data)[i])) GoodTrackSim;
            }
        }
        else
        {
            std::cout << "No data\n";
        }
    }

    ClassDef(TestClass,1);
};

#endif
EOF

cat <<EOF > test_tree.cpp
#include "test_tree.h"
#include "TFile.h"

ClassImp(BaseTrack);
ClassImp(BaseTrackSim);
ClassImp(GoodTrack);
ClassImp(GoodTrackSim);
ClassImp(TestClass);

int main()
{
    TFile * f = TFile::Open("test.root", "RECREATE");
    TTree * t = new TTree("T", "Tree");

    TestClass * tc = new TestClass;
    t->Branch("tc", &tc);

    f->Write();
    f->Close();
    return 0;
}
EOF

cat <<EOF > LinkDef.h
#ifdef __CINT__

#pragma link off all globals;
#pragma link off all classes;
#pragma link off all functions;
#pragma link off all enum;

#pragma link C++ nestedclass;
//-------------------------------------
// enums
#pragma link C++ namespace TestClass;
#pragma link C++ namespace BaseTrack;
#pragma link C++ namespace BaseTrackSim;
#pragma link C++ namespace GoodTrack;
#pragma link C++ namespace GoodTrackSim;

#endif
EOF

cat <<EOF > quick_run.sh
#!/bin/bash

rootcint -f Dict.cpp -c -p test_tree.h LinkDef.h
g++ test_tree.cpp Dict.cpp $(root-config --glibs --cflags)
./a.out
EOF

chmod +x quick_run.sh
./quick_run.sh

echo
echo
echo
echo "Created 'test_tree.h', 'test_tree.cpp' and 'LinkDef.h'"
echo "You can edit them and run './quick_run.sh' to compile and execute the example"

OK, I found what was the problem.

I didn’t make it working because of the virtual inheritance and the Warning mentioned above. So I change the idea to have interfaces. In the new hierarchy it is:

BaseTrack <-- GoodTrack <-- GoodTrackSim
                 BaseTrackSim <--|

Now, there are no virtual inheritances.

But to avoid making possible direct usage of BaseTrack, I declared them as a virtual (with pure virtual destructor), and this caused the issue that I had seg fault on TClonesArray::WriteStreamer(). I guess it was due to that there is requirement for ClassDef/ClassImp that object must be creatable and have default ctor.
So I removed pure virtual dtors and all started working. Now I must trust that users will not BaseTrack directly. :slight_smile:

I guess it was due to that there is requirement for ClassDef/ClassImp that object must be creatable and have default ctor.

Classes inheriting from TObject must have ClassDef and a dictionary (ClassImp is deprecated and not necessary and can be abstract classes and can have no default constructor nor any I/O constructor, however the most derived class must (of course :)) be concrete and have either a default constructor or a I/O constructor.

Note that to be used in a TClonesArray as hinted in the message, the inheritance from TObject must be leftmost. i.e. you need the following order:

class GoodTrackSim: public BaseTrackSim, public GoodTrack {};

Cheers,
Philippe.

About ClassImp is it also true for root-5? I forgot to mention the root version.

If I understand correct, leftmost is the first class after :, but in my original example both BaseTrackSim and GoodTrack were virtual inheritances from BaseTrack so order doesn’t matter. In my new solution with the interface, BaseTrackSim doesn’t inherits from any class, so proper order must be class GoodTrackSim: public GoodTrack, public BaseTrackSim {}. Or do we speak about something else?

Well it does … The meaning of the requirement is that with:

FullClass *obj = ....
TObject *tobject_ptr = obj;

you have the ‘numerical’ equality:

(long)obj == (long)tobject_ptr

Cheers,
Philippe.

In my new solution

My answer was indeed about the old code and I did miss the virtual part of the inheritance which indeed may (or may not) make it impossible to work …

In you new solution, I was not able to see where the TObject inheritance laid.

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