Trouble saving TObjArray to a TTree

Hello,

I have trouble saving/reading of TObjArray of my custom class to a TTree. I suspect the problem is with a dictionary, however, I can’t find anywhere how to do it properly.

The code of my script is:

#include "myclass.h"

int clones_test()
{
 
	gROOT->ProcessLine(".L myclass.C+");   

	TObjArray a(1);
	a[0] = new myclass();

	((myclass*)a[0])->track_length = 3;
	((myclass*)a[0])->v1 = new Float_t[3]{1.5,2,3};
	((myclass*)a[0])->v2 = new Float_t[3]{1.5,2,3};
	((myclass*)a[0])->v3 = new Float_t[3]{1.5,2,3};
	((myclass*)a[0])->v4 = new Float_t[3]{1.5,2,3};
	((myclass*)a[0])->v5 = new Float_t[3]{1.5,2,3};
	((myclass*)a[0])->v6 = new Float_t[3]{1.5,2,3};
	cout << ((myclass*)(a[0]))->v1[2] << endl;

	
	auto t = new TTree("t", "t");
	t->Branch("a", &a);
	t->Fill();
	t->Scan();
	
	return 0;
}

And I attach myclass.h and .C. After executing of the script, on Scan() I get:
Error in <Streamer>: Cannot stream interpreted class.

I would appreciate any help.

myclass.C (41 Bytes)
myclass.h (940 Bytes)


ROOT Version: 6.22.06
Platform: Not Provided
Compiler: Not Provided


I can reproduce the issue but I’m not quite sure what’s causing it. @vvassilev The error message comes from TCling, do you have an idea?

Create a simple “rootlogon.C” file (in your current working directory):

{ // rootlogon.C
  gROOT->ProcessLine(".L myclass.C+");
}

Thanks, that works. So I understand that for interpreted scripts libraries need to be loaded before their execution. It would be great if this was added to documentation or… fixed, if possible?

Still, after that I get a crash, and I am not sure why…

3 different issue. The message

Error in <Streamer>: Cannot stream interpreted class.

is due to the header being parsed before the library loaded. An alternative to Wile’s solution is, instead of the ProcessLine to add a the start of the script (before any parsing, for example on the first first line).

R__LOAD_LIBRARY(myclass.C+)

The crash is due to missing part in the declaration of myclass, you need to use:

	Float_t *v1 = nullptr; //[track_length]
	Float_t *v2 = nullptr; //[track_length]
	Float_t *v3 = nullptr; //[track_length]
	Float_t *v4 = nullptr; //[track_length]
	Float_t *v5 = nullptr; //[track_length]
	Float_t *v6 = nullptr; //[track_length]

or write a default constructor that initialize them. [The I/O uses the default constructor when it needs to create new object].

Third and likely most important, this is currently unlikely to be constructing what you need. Rather than distributing the content of the TObjArray with one branch per object, it creates a single branch (TObjArray can not even be split member-wise).

Instead you might have meant:

	t->Branch(&a);

which will create one branch per element of TObject array. In addition if you use something like:

class myclass : public TNamed
....
   myclass(const char *name) : TNamed(name, "") {}
....
   a[0] = new myclass("a0.");
   a[1] = new myclass("a1.");

the branch will have nice names (instead of all being “myclass” :frowning: ).

Cheers,
Philippe.

Thanks, that works!

An additional question is: it seems I do not need to init the class members in the constructor. I may as well just declare Int_t track_length=0;. Is there any reason to initialize track_length to 0 in a constructor?

Be warned that this problem is still unsolved, even in the newest ROOT 6.22/08:

@pcanal That’s why I started to “advertise” the usage of “rootlogon.C” files (and they also work in both ROOT 5 and 6 versions, if one needs to maintain this kind of compatibility for one’s macros).

BTW. For safety reasons, I would do both in the “default constructor”, set all pointers and the length to zero (well, you never know when something will try to be clever and use or “delete” them). Also, make sure that you do not allocate anything in the “default constructor” (this space will be lost by the ROOT I/O).

Is there any reason to initialize track_length to 0 in a constructor?

Yes, unless you can be 100% sure that the ‘random’ state will never be (inadvertently) used. (In this you don’t).

My point is, that (from what I read) you can initalise variables outside of constructor since C++11 - member initialisation. Just declare it as int track_length=0;. I wonder if intialising inside a constructor instead of “member initialisation” outside of it has any advantages.

In term of performance it is the same.

In term of code maintenance, having the assign next to the member has the following advantages (at least):

  • Single location for the default value used by all constructor:
    ** Reduce copy-pasting
    ** Clearer documentation of the default value
  • Avoid forgetting to set the default value when writing new constructor
  • Easier way to spot if an initialization is missing.
  • etc. :slight_smile:

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