Renaming all histograms in the directory: SetName() is weird

Hi all,

I’m trying to mass rename all histograms in the working directory after they had been created, but I am running into problems.
Here’s what I do:

int rename() {
	TH1D* hist1 = new TH1D("hist1","",10,0,10);
	TH1D* hist2 = new TH1D("hist2","",10,0,10);
	TH1D* hist3 = new TH1D("hist3","",10,0,10);

	cout << "addresses are " << hist1 << " , " << hist2 << " , " << hist3 << endl;

	TList* histList = gDirectory->GetList();
	Int_t iHist = 0; while(histList->At(iHist)) {
		cout << "iHist: " << iHist << " currently points at " << histList->At(iHist) << endl;
		TNamed* named = (TNamed*)histList->At(iHist);
		TString histName(named->GetName());
		if (histName.BeginsWith("hist")) {
			histName = "prefix_" + histName;
			named->SetName(histName);
			cout << "iHist: " << iHist << " now points at " << histList->At(iHist) << endl; }
		iHist++;
	}
	gDirectory->ls();
}

But it doesn’t work. Somehow, changing the name of the first TNamed suddenly makes the TList::At(iHist) point at the second object, so then when you increment iHist for the second iteration of the loop, you work with the third histogram instead of the second. Here is the output so that you can see for yourself:

addresses are 0x3a04f70 , 0x3a05d00 , 0x3a06560
iHist: 0 currently points at 0x3a04f70
iHist: 0 now points at 0x3a05d00
iHist: 1 currently points at 0x3a06560
iHist: 1 now points at 0x3a04f70
iHist: 2 currently points at 0x3a06560
 OBJ: TH1D	hist2	 : 0 at: 0x3a05d00
 OBJ: TH1D	prefix_hist1	 : 0 at: 0x3a04f70
 OBJ: TH1D	prefix_hist3	 : 0 at: 0x3a06560

Is this intended behavior? If yes, does anyone have any idea how can I work around this? Or achieve the same thing using perhaps an altogether different method?

Thanks!


ROOT Version: 5.34
Platform: Ubuntu 16.04


TNamed::SetName
TH1::SetName

I’m not sure if it’s really kosher but you could simply try to use:

		else iHist++;
	}

Maybe a better idea would be to go from the last to the first histogram:

	Int_t iHist = histList->GetLast(); while(iHist > -1) {
		// ...
		iHist--;
	}

Hi Wile,

Thanks for your answer. Do I understand correctly that

  1. since TNamed::SetName is virtual , the TH1::SetName is used instead and
  2. since TH1::SetName in its definition removes the old object from the gDirectory and appends the new one at the end, this makes the ->At(0) point at the second object instead etc. ?

Thank you also for your suggestions, both of them work.
The first one feels unsafe, particularly because if I’ll later want to also rename other types whose ::SetName doesn’t suffer from this problem (e.g. TGraph), I’ll run into problems.
The second suggestion should work either way so I’ll go with it.

Thank you!

P.S. If anybody has any ideas for other, perhaps less janky, mechanisms of mass renaming objects, please let me know!

What happens if you just loop over the TObject* pointers in the list?

for (auto histPtr : *gDirectory->GetList()) {
    static_cast<TNamed*>(histPtr)->SetName(...);
}

(not 100% sure this will work, but I don’t see why not, so maybe it’s worth a try)

Thanks for the suggestion, unfortunately I am unable to test it out now since I run on ROOT 5.34, so I can’t use C++11 features.

The 5.34 equivalent would use TIter:

TIter it(gDirectory->GetList());
while ((TObject *obj = it()))
  ((TNamed*)obj)->SetName(...);

Thanks, using a TIter works in the sense that it renames all objects successfully. However, instead of 3 iterations, the loops goes over 6 (the renaming still appends the object to the end of the list and thus the iterator goes over it twice).

This can be worked around though by using some new conditions such as counting the iterations…

Try: TIter it(gDirectory->GetList(), kIterBackward);

1 Like

Haha well this is getting interesting

instead of 3 iterations

Note that the version that uses At actually also iterates more than once: every At calls starts an iteration from the beginning of the list until the desired element, so you are looking at 1 element for At(0), then 2 for At(1), then 3 for At(2)

I’d suggest to just copy gDirectory->GetList() in a new TList that does not change while you are iterating.

Both iterating backwards and copying into a new TList work well :slight_smile:
Thanks a lot!

By the way, if anybody knows why does the TH1::SetName have this behaviour implemented, whereas e.g. TGraph::SetName doesn’t, please let me know :slight_smile:

I guess TH1's are automatically registered in gDirectory while TGraph's are not.
So when you change name to a TH1 it gets automatically re-registered in the gDirectory with the new name.
In case you don’t need the renamed histograms to be registered in the gDirectory, you can de-register them manually just after creation with hist->SetDirectory(nullptr). Which would also solve the original issue.

Cheers,
Enrico

1 Like

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