Saving custom classes in ROOT6

Hello,

my program uses ROOT to save intermediate analysis results to root files using ExRootAnalysis.
The classes I am saving are mainly a collection of member variables of type float and some functions filling these members. I am using the ClassDef macro and avoid code duplication by inheriting common members from base classes. I do not define any streamers.

Recently I have switched to ROOT6.
I am generating the dictionary as before, additionally I have moved the new *_rdict.pcm files in my library folder. But when I run my program I get the error message

and the resulting files have a more complicated structure resembling the top most inheritance step.
Which of course prevents later analysis to access the branches via TTree::SetBranchStatus and TTree::SetBranchAddress

What is happening here, and how can I fix it?

Somebody else has already encountered this problem here [url]ROOT v6.02 TCling::RegisterModule pcm dictionnary but I can not reproduce his solution

Best regards,
Jan Hajer

Hi Jan,

it is not possible to split instances of classes with a custom streamer. The streamer method implemented by the user is “a black box” for ROOT which is called to (de)serialse the objects performing user defined actions: the single data members cannot be stored in different “columns”.
What Valerian describes in the post you cite is related but it does not have a big overlap with your issue.

Cheers,
Danilo

Hi Danilo,

I am afraid, that i do not really understand what you are trying to explain to me.
What do you mean be splitting instances of classes? I have only an inheritance hierarchy, which did not show up in ROOT5 files, and I want it to stay like this also with ROOT6 files. Additionally, I have not implemented a streamer method other than what the ClassDef macro is implementing.

Thanks for helping me,
Jan

Hi,

so the problem is that the class which root says it has a custom streamer has no custom streamer: is this the problem?
If yes, could you post a reproducer for this issue?

Danilo

Hi,

here is a minimal program (main.cpp) which saves triples of random numbers to a class Three (Numbers.hh) inherited from the class Two (Numbers.hh).
In my setup I use CMake to build the files and generate the necessary dictionaries from Numbers.cpp using TestLinkDef.hh.
The output test.root contains the triples of random numbers but they are split up according the the class inheritance (Three<-Two).
With Root5 this gives flat files, containing just the triples.

Best,
Jan
test.root (6.62 KB)
Numbers.hh (437 Bytes)
TestLinkDef.hh (85 Bytes)
Numbers.cpp (428 Bytes)
main.cpp (633 Bytes)

Hi,

looking at your linkdef, you are missing trailing “+” after the selection pragmas, which allow rootcling to generate automatic streamers for your classes. The usage of “+” is reccomended for all new classes (see rootcling -h for all the details). I attach a standalone, i.e. no atlas class needed, version of your example for future reference.

Cheers,
Danilo

LinkDef.h

#ifdef __MAKECINT__
#pragma link C++ class Two+;
#pragma link C++ class Three+;
#pragma link C++ class vector<Two>+;
#pragma link C++ class vector<Three>+;
#endif

main.cpp

#include <vector>
#include "TFile.h"
#include "TTree.h"
#include "TRandom.h"
#include "Numbers.hh"

int main()
{
    TFile file("test.root", "recreate");
    TTree tree("tree","tree");
    
    Two two;
    tree.Branch("two", &two);
    
    std::vector<Two> twos;
    tree.Branch("twos", &twos);
    
    std::vector<Three> threes;
    tree.Branch("threes", &threes);
    TRandom random;
    for (int i = 0; i < 10; ++i) {
        threes.push_back(Three(random.Rndm(), random.Rndm(), random.Rndm()));
        twos.push_back(Two(random.Rndm(), random.Rndm()));
        two = twos[0];
        tree.Fill();   
    }
    tree.Write();
    file.Close();
}

Numbers.hh

#ifndef __CLASSES__
#define __CLASSES__
#include "TObject.h"

class Two: public TObject
{
private:
    float X;
    float Y;
public:
    Two();
    Two(float x, float y):X(x),Y(y){};
    ~Two();    
    void SetX(float x);
    void SetY(float y);
    void Set(float x, float y);
    ClassDef(Two, 10)
};


class Three : public Two
{
private:
    float Z;
public:
    Three();
    Three(float x, float y , float z);
    void SetZ(float z);
    void Set(float x, float y , float z);
    ClassDef(Three, 1)
};


Two::Two()
{
    X = 0;
    Y = 0;
}
Two::~Two() {}
void Two::SetX(float x)
{
    X = x;
}
void Two::SetY(float y)
{
    Y = y;
}
void Two::Set(float x, float y)
{
    SetX(x);
    SetY(y);
}
Three::Three()
{
    Z = 0;
}
Three::Three(float x, float y, float z)
{
    Set(x, y, z);
}
void Three::SetZ(float z)
{
    Z = z;
}
void Three::Set(float x, float y, float z)
{
    Two::Set(x, y);
    SetZ(z);
}

#endif

Oh,

That was embarrassingly simple. This is probably the most common LinkDef mistake.
I must admit that I introduced this problem after switching to ROOT6 because my old LinkDef stopped working.
I define everything that needs a dictionary in one file lets say “Numbers.hh” and I used to have a really simple LinkDef which I never had to update

#ifdef __MAKECINT__
#pragma link C++ defined_in "Numbers.hh";
#endif

But somehow this doesn’t work anymore. I would prefer if I could keep this simple file as it reduces the possibilities for errors, such as forgetting the ‘+’.
Do you have any idea what has changed with the “defined in” pragma?

Nonetheless, thanks a lot
Jan

Hi Jan,

something odd is going on: the defined_in pragma should work as expected.
Using this linkdef (identical to yours):

#ifdef __MAKECINT__
#pragma link C++ defined_in "Numbers.hh";
#endif

this command

rootcling -f  Numbers_dict.cxx Numbers.hh linkdef.h

and the Numbers.h file I posted previously, the “Two” and “Three” classes are correctly selected.
Why you say they are not?

Danilo

Yes,

you are right, generating dictionaries by hand from the folder where the LinkDef is located works well.
But when I generate the dictionary from a different folder I have to give the full path to the Numbers.hh in the LinkDef.hh. I didn’t have to do that before.
I do generate the dictionary from a different folder because I am using CMake which processes all the source files from a separate build folder.
I am passing the include folders via “-I” to rootcint/rootcling but apparently it stopped picking up the include paths.

Best,
Jan

Hi Jan,

I will look into this.
For the moment, is it possible to modify the rootcling invocation in your cmake moving the directory for inclusion to the absolute path to the header?

Danilo

Hi,

yes right now I call the header file with the long relative path from the build directory to the include directory. This works, but is more error prone than before.
For me this is not an urgent problem any more.

Thanks for your help,
Jan