How to implement a pointer member variable for Write/Clone

Dear Experts,

I have a question about the proper method for writing a ROOT compatible class to a TFile. In particular, for a class containing a pointer which it may or may not own. A basic example of the code is below.

A class MyClass has a member variable MyClass::fPointedClass which is a pointer to MyPointedClass. Depending on the use case, MyClass is either responsible for deleting MyClass::fPointedClass or not. This is achieved in the constructor, where a MyPointedClass pointer is passed as an optional argument. If a MyPointedClass pointer is passed, MyClass just refers to the external copy, and sets a variable MyClass::fOwnPointed = kFALSE. Otherwise, MyClass constructs its own MyPointedClass, and sets the variable MyClass::fOwnPointed = kTRUE. When the destructor is called, MyClass::fPointedClass is only deleted if MyClass::fOwnPointed == kTRUE.

My question is how to treat this when using MyClass::Write() to save MyClass to a TFile. Because MyClass::fPointedClass generally isn’t needed after reading MyClass from a file (in the usual use cases) I just need something that doesn’t break. For example, after reading a MyClass from a TFile, the MyClass::fPointedClass doesn’t necessarily point to something, but MyClass::fOwnPointed can be kTRUE, which causes problems for the copy constructor etc.

The simplest behavior seems to be having MyClass::fOwnPointed always be kTRUE, and save a separate copy of MyClass::fPointedClass saved for each MyClass, or a new one made each time. Perhaps using “//!” so that the two class variables (MyClass::fOwnPointed and MyClass::fPointedClass) aren’t included by the streamer, and then initializing them in the default constructor? I don’t know if this works with the current ROOT I/O setup though; any suggestions on the best way to accomplish this would be welcomed.

I’m also curious if I can get the correct behavior for MyClass::Clone(). In the case that MyClass::fOwnPointed is kFALSE, it works fine. However, for MyClass::fOwnPointed == kTRUE, a new MyClass::fPointedClass isn’t created for the cloned object. Any suggestions on how to accomplish this?

Thanks a lot for your help!
Chris

#include "TNamed.h"

class MyPointedClass : public TNamed {
   MyPointedClass(const char *name, const char *title) : TNamed(name, title) { }
   MyPointedClass() : TNamed() { }
   virtual ~MyPointedClass() { }
   ClassDef(MyPointedClass, 1);
}

class MyPointedClass : public TNamed {
private:
   MyPointedClass *fPointedClass;
   Bool_t fOwnPointed;

public:
   MyClass(const char *name, const char *title, MyPointedClass *pointed = NULL) : TNamed(name, title) {
      if (pointed) {
         fPointedClass = pointed;
         fOwnPointed = kFALSE;
      } else {
         fPointedClass = new MyPointedClass("pointed", "pointed");
         fOwnPointed = kTRUE;
      }
   }
   MyClass() : TNamed(), fPointedClass(NULL), fOwnPointed(kFALSE) { }
   virtual ~MyClass() {
      if (fOwnPointed) delete fPointedClass;
   }
   ClassDef(MyClass, 1);
}

[quote]My question is how to treat this when using MyClass::Write() to save MyClass to a TFile. Because MyClass::fPointedClass generally isn’t needed after reading MyClass from a file (in the usual use cases) I just need something that doesn’t break.[/quote]If the pointed to object is not needed to be stored/retrieved the simplest is indeed to mark both member as transient. In that you can also control what happen to those members when an object is read in by setting up a read rule:#pragma read sourceClass="MyClass" targetClass="MyClass" source="" version="[1-]" target="fOwnPointed" \ code="{ if (fOwnPointed) { newObj->some_thing_to_reset_the_object(); } else { fOwnPointed = true; fPointedClass = new MyPointedClass(); }"(which the rule, the object will be left (this might be the right behavior) with whatever it was constructed with, for example by the default constructor).

Cheers,
Philippe.

Dear Philippe,

Thanks a lot for your answer, this seems like it will do what I want. I’m having a bit of trouble implementing it though. I have a file which contains a previous version (1) of MyClass, written without “//!” in the header file after MyClass::fOwnPointed and MyClass::fPointedClass. After reading in the object, MyClass::fOwnPointed = true, and MyClass::fPointedClass = (nil). Before calling Write(), MyClass::fOwnPointed was true, and MyClass::fPointedClass pointed to something. The destructor of MyClass was called after MyClass::Write() and TFile::Close() were called.

I then put the following rules in my LinkDef.h file:

LinkDef.h

#include "MyClass/MyClass.h"

#ifdef __CINT__
#pragma link off all globals;
#pragma link off all classes;
#pragma link off all functions;
#pragma link C++ nestedclasses;

#pragma link C++ class MyPointedClass+;
#pragma link C++ class MyClass+;

#pragma read \
   sourceClass="MyClass" \
   version="[1-]" \
   source="" \
   targetClass="MyClass" \
   target="fPointedClass" \
   targetType="MyPointedClass*" \
   code="{ fPointedClass = new MyPointedClass(); }"

#pragma read \
   sourceClass="MyClass" \
   version="[1-]" \
   source="" \
   targetClass="MyClass" \
   target="fOwnPointed" \
   code="{ fOwnPointed = false; }"

#endif

Here, both MyClass and MyPointedClass are defined in MyClass.h, similar to the example in my first post. When I try to read the file, using MyClass version 2 (which contains //! after fPointedClass and fOwnPointed, and is compiled with the LinkDef.h code above) I get MyClass::fOwnPointed = false, and MyClass::fPointedClass = (nil).

So, the second rule worked (setting fOwnPointed = false) but the first rule (making a new fPointedClass) doesn’t. I don’t get any complains when compiling version 2, with the updated LinkDef.h file. Note, once the first rule works I’ll change fOwnPointed to true; this was just to troubleshoot if I could change anything using pragma read.

Any thoughts what might be going wrong?

Thanks!
Chris

Hi,

In the first rule the part targetType="MyPointedClass*" \should be removed. Also (given your description) it seems ‘dangerous’ to have this rule do an unconditional allocation (i.e. if the object that is being used to read was owning the pointed to object, then it would be leaked).

Cheers,
Philippe.

Hello,

Great, removing that line makes things work like expected. I’m now trying to get a bit fancier, and running into problems. When making a new MyPointedClass() I’d like to pass it an argument, using onfile.fN (where fN is a member of MyClass). Even specifying that in “input” gives me problems though (fleshing out the MyClass definition a bit):

MyClass.h

#include "TArrayI.h"
#include "TH1D.h"
#include "TNamed.h"

class MyPointedClass : public TNamed, public TArrayI {
public:
   MyPointedClass(const char *name, const char *title, Int_t N) : TNamed(name, title), TArrayI(N) { }
   MyPointedClass() : TNamed(), TArrayI() { }
   virtual ~MyPointedClass() { }
   ClassDef(MyPointedClass, 1);
}

class MyClass : public TNamed {
private:
   MyPointedClass *fPointedClass; //!
   Bool_t fOwnPointed; //!
   Int_t fN;
   TH1D **fHistos; //[fN]

public:
   MyClass(const char *name, const char *title, Int_t N, MyPointedClass *pointed = NULL) : TNamed(name, title), fN(N) {
      if (pointed) {
         fPointedClass = pointed;
         fOwnPointed = kFALSE;
      } else {
         fPointedClass = new MyPointedClass("pointed", "pointed");
         fOwnPointed = kTRUE;
      }
      fHistos = new TH1D*[fN];
      for (Int_t i = 0; i < fN; ++i) {
         fHistos[i] = new TH1D("hist", "hist", 10, 1, 10);
         fHistos[i]->SetDirectory(0);
      }
   }
   MyClass() : TNamed(), fPointedClass(NULL), fOwnPointed(kFALSE), fHistos(NULL) { }
   virtual ~MyClass() {
      if (fOwnPointed) delete fPointedClass;
      for (Int_t i = 0; i < fN; ++i) delete fHistos[i];
      delete [] fHistos;
   }
   ClassDef(MyClass, 1);
}

LinkDef.h

#pragma read \
   sourceClass="MyClass" \
   version="[1-]" \
   source="int fN" \
   targetClass="MyClass" \
   target="fPointedClass" \
   code="{ fPointedClass = new MyPointedClass(\"name\", \"title\", 10000); }"

In this case, I’d like to eventually replace “10000” with “onfile.fN” When reading in the class from the file, I get the following errors:

Error in <TExMap::Remove>: key 1351328379 not found at 472
Warning in <TBufferFile::CheckObject>: reference to unavailable class TH1D, pointers of this type will be 0

However, if I leave source="" I don’t have any problems. Am I doing some wrong, getting at the MyClass::fN variable?

I’m also having trouble combining the two rules, to make things less dangerous; I’ll follow up on this a bit later once the easy case is working.

Thanks,
Chris

Hi Chris,

In this failing case what doesTClass::GetClass("MyClass")->GetStreamerInfos()->ls()print to the screen?

Cheers,
Philippe.

Hi Philippe,

The name of the classes I’m running on are slightly different from the above MyClass example. If it’s not clear what’s happening from the below I can share the full code, or compile and run the MyClass example and dump that (let me know which you prefer).

The mapping between the two are:
MyPointedClass --> BootstrapGenerator --> MyPointedClass
TH1DBootstrap inherits from TH1DBootstrap --> MyClass

Here, TH1Bootstrap includes the fNReplica --> fN, fOwnGen --> fOwnPointed, and fGenerator --> fPointedClass (a BootstrapGenerator). TH1DBootstrap inherits from TH1Bootstrap, and includes fHistReplica --> fHistos. For fHist (a single histogram) there is no analog in the MyClass example.

For the failing case, I see:

Error in <TExMap::Remove>: key 117440512 not found at 187
Warning in <TBufferFile::CheckObject>: reference to unavailable class TH1D, pointers of this type will be 0

TClass::GetClass(“TH1DBootstrap”)->GetStreamerInfos()->ls()

OBJ: TObjArray	TObjArray	An array of objects : 0

StreamerInfo for class: TH1DBootstrap, version=4, checksum=0x3a8bc4de
  TH1Bootstrap   BASE            offset=  0 type= 0                     
  TH1D*          fHist           offset= 88 type=64                     
  TH1D**         fHistReplica    offset= 96 type=501 [fNReplica]         
   i= 0, TH1Bootstrap    type=  0, offset=  0, len=1, method=0
   i= 1, fHist           type= 64, offset= 88, len=1, method=0
   i= 2, fHistReplica    type=501, offset= 96, len=1, method=72

StreamerInfo for class: TH1DBootstrap, version=6, checksum=0x3a8bc4de
  TH1Bootstrap   BASE            offset=  0 type= 0                     
  TH1D*          fHist           offset= 88 type=64                     
  TH1D**         fHistReplica    offset= 96 type=501 [fNReplica]         
   i= 0, TH1Bootstrap    type=  0, offset=  0, len=1, method=0
   i= 1, fHist           type= 64, offset= 88, len=1, method=0
   i= 2, fHistReplica    type=501, offset= 96, len=1, method=72

TClass::GetClass(“TH1Bootstrap”)->GetStreamerInfos()->ls()

OBJ: TObjArray	TObjArray	An array of objects : 0

StreamerInfo for class: TH1Bootstrap, version=5, checksum=0x40ee0541
  TH1Bootstrap@@5 @@alloc         offset=  0 type=1001 The basis for a named object (name, title)
  TNamed         BASE            offset=  0 type=67                     
  Int_t          fNReplica       offset= 72 type= 6  (cached,repeat)                     
  Int_t          fNReplica       offset= 64 type= 6                     
  BootstrapGenerator* fGenerator      offset=99999 type=64                     
  Bool_t         fOwnGen         offset=99999 type=18                     
  BootstrapGenerator fGenerator      offset= 72 type=1000                     
  Bool_t         fOwnGen         offset= 80 type=1000                     
  TH1Bootstrap@@5 @@dealloc       offset=  0 type=1002                     
   i= 0, @@alloc         type=1001, offset=  0, len=1, method=0
   i= 1, TNamed          type= 67, offset=  0, len=1, method=0
   i= 2, fNReplica       type=  6, offset= 72, len=1, method=11399768 [cached,repeat]
   i= 3, fNReplica       type=  6, offset= 64, len=1, method=10856072
   i= 4, fGenerator      type=164, offset=99999, len=1, method=0
   i= 5, fOwnGen         type=118, offset=99999, len=1, method=0
   i= 6, fGenerator      type=1000, offset= 72, len=1, method=0
   i= 7, fOwnGen         type=1000, offset= 80, len=1, method=0
   i= 8, @@dealloc       type=1002, offset=  0, len=1, method=0

StreamerInfo for class: TH1Bootstrap, version=8, checksum=0xf96997a2
  TH1Bootstrap@@8 @@alloc         offset=  0 type=1001                     
  TNamed         BASE            offset=  0 type=67 The basis for a named object (name, title)
  Int_t          fNReplica       offset= 72 type= 6  (cached,repeat)                     
  Int_t          fNReplica       offset= 64 type= 3                     
  BootstrapGenerator fGenerator      offset= 72 type=1000                     
  Bool_t         fOwnGen         offset= 80 type=1000                     
  TH1Bootstrap@@8 @@dealloc       offset=  0 type=1002                     
   i= 0, @@alloc         type=1001, offset=  0, len=1, method=0
   i= 1, TNamed          type= 67, offset=  0, len=1, method=0
   i= 2, fNReplica       type=  3, offset= 72, len=1, method=0 [cached,repeat]
   i= 3, fNReplica       type=  3, offset= 64, len=1, method=0
   i= 4, fGenerator      type=1000, offset= 72, len=1, method=0
   i= 5, fOwnGen         type=1000, offset= 80, len=1, method=0
   i= 6, @@dealloc       type=1002, offset=  0, len=1, method=0

TClass::GetClass(“BootstrapGenerator”)->GetStreamerInfos()->ls()

OBJ: TObjArray	TObjArray	An array of objects : 0

StreamerInfo for class: BootstrapGenerator, version=1, checksum=0x1ee26e62
  TNamed         BASE            offset=  0 type=67 The basis for a named object (name, title)
  TArrayI        BASE            offset=  0 type= 0 Array of ints       
  Int_t          fNReplica       offset=  0 type= 3 Number of replicas to store
  TRandom*       fGenerator      offset=  0 type=64

Thanks,
Chris

Hi Chris,

Everything looks as expected, so there might be a deficiency in the handling of a counter (fNReplica) when it is used as a rule source. To make progress I would need a complete running example reproducing the problem.

Thanks,
Philippe.

Hi Philippe,

I’ve put a working copy of the code here: /afs/cern.ch/user/c/cjmeyer/public/BootstrapGenerator. In this directory is also a README file which includes instructions for running, compiling, and a basic overview of the structure for convenience. There’s a decent number of class functions I can remove, to simplify things. Let me know if this would be useful.

The example included gives the TExMap::Remove and TBufferFile::CheckObject errors, as well as dumping the stream info.

Thanks a lot for taking a look at this,
Chris