Adding ROOT I/O features to a class inheriting from a ROOT-unaware class

Hi all,

I have a ROOT-unaware class that I use for loading and applying configurations (let’s call it Configurable). Now, I’d like to make ROOT I/O capable classes that can use this base class. I plan on having a common base class for these (let’s call it TConfigurable) that will take care of interactions between more derived classes and Configurable.
I’m using the cmake command reflex_generate_dictionary to create the dictionary files.

What I have at the moment is something like

// TConfigurable.h
#include "Configurable.h"
class TConfigurable : public TNamed, Configurable {
  public:
    TConfigurable() {}
    ClassDef(TConfigurable, 1)
};

//TConfigurable.cxx
ClassImp(TConfigurable)

At the moment when I call TConfigurable::Write I, unsurprisingly get an error message like

TStreamerInfo::Build:0: RuntimeWarning: TConfigurable: base class Configurable has no streamer or dictionary it will not be saved

and a whole host of issues if I then try and use the file…

Configurable has various member variables, none of which I want written out to a file. I’m less fussed over whether or not they are usable in an interactive session (I’d probably lean on the side of not wanting them to appear there either). I’d like some way of telling the dictionary generation procedure to ignore the Configurable base class, so that as far as ROOT is concerned, TConfigurable is all it ever sees.

Is there a good way to do this?

Cheers,
Jon


ROOT Version: 6.14/04
Platform: Not Provided
Compiler: g++ (GCC) 6.2.0


Hi,

if I understand correctly, you would like to have in memory a representation of your class, say class A, while on disk, you’d like to see a different representation, say class B (" I’d like some way of telling the dictionary generation procedure to ignore the Configurable base class, so that as far as ROOT is concerned, TConfigurable is all it ever sees.")

Is this correct?

Cheers,
D

Hi,

Kind of… Basically, the data held in Configurable is all transient and calculated either at compile-time or at run-time once the configuration file has been loaded. I could make this class ROOT aware and then prevent those data members from being added to the streamer but that seems somewhat inelegant - for example, it prevents me from re-using the Configurable class in a project which does not use ROOT.

Cheers,
Jon

Hi,

I could make this class ROOT aware and then prevent those data members from being added to the streamer but that seems somewhat inelegant - for example, it prevents me from re-using the Configurable class in a project which does not use ROOT.

I do not see why.
Irrespective of that, perhaps you may rething your design. Why don’t you write on disk a struct per configurable, say, TProtoConfigurable and initialise your TConfigurable with it? You would then completely decouple the IO layer from the in memory layer.

Cheers,
D

You do not need to change you header to be able to make ROOT aware of your class, you ‘just’ need to generate a dictionary for it.

To make all the data member transient is as simple as using :

#pragma link C++ options=version(0) class Configurable+;

when using rootcling or

<class name="pat::JetCorrFactors"  ClassVersion="0"\>

when using genreflex. (And if you need to mark only some of the member transient you can do that we a innocuous looking comment for rootcling and some xml statements for genreflex).

And remember that you can do I/O on any class, they do not need to inherit from TObject nor TNamed (unless you need that for some other reasons) and do not need a ClassDef (albeit the I/O is marginally faster with a ClassDef).

Cheers,
Philippe.

Hi Philippe,

That works almost perfectly! I can now create and write the objects fine.
However, there’s still an error message when I read one of my objects from file

Error in <TBufferFile::CheckByteCount>: object of class RDFPlotting::ConfigurableObject read too few bytes: 2 instead of 6

(RDFPlotting::ConfigurableObject is what I was referring to as Configurable before).

The creation of ROOT dictionaries etc is still somewhat alien to me and I’ve not found much more documentation than in the user’s guide (https://root.cern.ch/root/htmldoc/guides/users-guide/AddingaClass.html).

Cheers,
Jon

I should also add that (as far as can tell with the very limited tests that I’ve run so far) this error message doesn’t actually matter too much. The data from TConfigurable is still usable.

Also @dpiparo, I had thought that in order to prevent Configurable from being written out I might need to use ClassDef in its header, thankfully this isn’t the case :slight_smile:. I’m not sure what you mean by your second comment though, so long as I inherit from Configurable I need to have some way to tell ROOT not to try and stream it right?

Cheers,
Jon

Then something is still wrong :frowning: … Was the dictionary loaded during both reading and writing?

With my build directory in LD_LIBRARY_PATH:

root [1] tconf = RDFPlotting::TConfigurable()
(RDFPlotting::TConfigurable &) Name:  Title:
root [2] tconf.SetName("MyConf")
root [3] tconf.SetTitle("MyConf")
root [4] tconf.data = 20
(float) 20.0000f
root [6] fp = TFile::Open("Test2.root", "CREATE")
(TFile *) @0x7fffe1fede28
root [7] tconf.Write()
(int) 117
root [8] fp->Close()
root [9] fp = TFile::Open("Test2.root")
(TFile *) @0x7fffe1fede08
root [10] fp->Get("MyConf")
Error in <TBufferFile::CheckByteCount>: object of class RDFPlotting::ConfigurableObject read too few bytes: 2 instead of 6
(TObject *) 0x4377e90
root [11]

I guess I’m missing a step to properly load the dictionary?

Note that IF a class inherits from TObject (directly or indirectly), it must have a ClassDef macro to be stored properly. (So in your original example Configurable does not need a ClassDef but TConfigurable does.

Cheers,
Philippe.

TConfigurable does have a ClassDef

// TConfigurable.h
#ifndef RDFPlotting_TConfigurable_H
#define RDFPlotting_TConfigurable_H

#include <TNamed.h>
#include "RDFPlotting/ConfigurableObject.h"

namespace RDFPlotting {
  class TConfigurable : public TNamed, public ConfigurableObject {
    public:
      /// Empty constructor for I/O purposes
      TConfigurable() {};

     float data;
    private:
      ClassDef(TConfigurable, 1)
  };
}

#endif //> !RDFPlotting_TConfigurable_H
//TConfigurable.cxx
#include "RDFPlotting/TConfigurable.h"

namespace RDFPlotting {
  ClassImp(TConfigurable)

}

Humm … then I am missing something. Could you send me a reproducer so that we can fix the problem (in either your code or ROOT)?

Sure - I’ll try and get something minimal together - thanks!

MWE.tar.gz (715 Bytes)

Firstly, sorry for the delay - I was out for a few hours.

You can reproduce it with something very minimal indeed!
To reproduce

mkdir build source run
tar -C source -zxf MWE.tar.gz
cd build
cmake ../source
make
LD_LIBRARY_PATH=$(pwd):${LD_LIBRARY_PATH}
cd ../run
python
>>> import ROOT
>>> obj_write = ROOT.TBase()
>>> obj_write.SetName("MyObj")
>>> f_write = ROOT.TFile.Open("test.root", "CREATE")
>>> obj_write.Write()
>>> f_write.Close()
>>> f_read = ROOT.TFile.Open("test.root")
>>> obj_read = f_read.Get("MyObj")

And on that last line you should see the error message:

Error in <TBufferFile::CheckByteCount>: object of class Base read too few bytes: 2 instead of 6

Cheers,
Jon

Hi Jon,

It turns out that ‘class version 0’ is not available for classes with ClassDef and we have not yet implemented an xml tag to mark the class as defaulting to transient member. So instead of setting the class to 0, you ‘just’ need to enumerate all the data members like:

          <class name="Configurable" >
            <field name="m_one" transient="true"/>
            <field name="m_two" transient="true"/>
            <field name="m_three" transient="true"/>
            <field name="m_just_demonstrating_an_alternative_spelling" persistent="false"/>
          </class>

Cheers,
Philippe.

Hi Philippe,

Just to confirm that this solves the problem, I no longer see any error message upon loading the object from file.

Thanks so much for all your help!
Jon

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