Cannot create a proper TMap

I am trying to create a TMap. The keys are going to be strings, the values vectors with 3 elements. I have created a code which is creating exactly that:

void mapTest() {

  TFile * file = TFile::Open("mapTest.root", "RECREATE");

  TMap * m = new TMap();

  Int_t n = 10;
  std::vector<TObjString> vKey (n);                // vector of the keys
  std::vector< TVectorT<Double_t> > vValue (n);

  for (Int_t i=0; i<n; i++) {
    Double_t value[] = { (Double_t )i, (Double_t) i+1, (Double_t) i+2.5};
    vValue[i].Use(3, value);

    std::string key = std::to_string(i);
    vKey[i] = TObjString( key.c_str() );

    m->Add( &(vKey[i]), &(vValue[i]) );
  m->Write("myMap", 1);
  }
}

But this is not correct for some reason.
After inspecting the content of my TMap I found that all values are identical to the last value, i.e. vVector[9].

kali: /mnt/e15/vogl/DriftTimeStudies$ root mapTest.root 
root [0] 
Attaching file mapTest.root as _file0...
(TFile *) 0x1f1c050
root [1] g = *(TVectorT<Double_t>*) myMap->GetValue("5")
(TVectorT<double> &) Name: TVectorT<double> Title: 
root [2] g[0]
(double) 9.000000
root [3] h = *(TVectorT<Double_t>*) myMap->GetValue("9")
(TVectorT<double> &) Name: TVectorT<double> Title: 
root [4] h[0]
(double) 9.000000

Any help would be appreciated!

Hi,
m->Write is inside the for loop! :smiley: is that intended?

I don’t see anything else suspicious…

So, I think what happened was that

  1. your program contained undefined behavior because the double[] is freed at each iteration, but each TVectorT points to the deleted area of memory
  2. the ROOT interpreter happend to always return the same area of memory for value at each iteration, and it didn’t overwrite it in the end, so all vectors were reading the same (freed but still readable) slab of memory at the time of writing to file

This works but leaks memory, you have to delete what the TVectorT point to:

void mapTest() {
  TFile* file = TFile::Open("mapTest.root", "RECREATE");
  TMap m;

  int n = 3;
  std::vector<TObjString> vKey(n);
  std::vector<TVectorT<double>> vValue(n);

  for (int i=0; i<n; ++i) {
      std::cout << "VECTOR " << i << std::endl;

      //with this, every TVectorT ends up pointing to the same area of memory
      //double value[] = {double(i), i+1., i+2.5};
      //with this, every TVectorT points to a different area of memory
      const double *value = new double[3]{double(i), i+1., i+2.5};
      vValue[i].Use(3, value);
      std::cout <<vValue[i].IsOwner() << std::endl;
      vKey[i] = std::to_string(i).c_str();
      m.Add(&vKey[i], &vValue[i]);
  }

  for (auto &v : vValue)
     std::cout << &v[0] << std::endl;

  m.Write("myMap", 1);
  file->Close();
}

The significant change is what I commented inline.

Cheers,
Enrico

1 Like

Hi Enrice,

thank you very much for your answer. This indeed solves the problem!

I do however have a follow up question to that regarding TVectorT, if that is OK. It seems like I don’t understand that class completely (or C++ for that matter):
Why did my code compile in the first place?
In the line

vValue[i].Use(3, value)

I call one of the functions “Use” with two arguments where the first one is an Int_t, so regarding to the documentation of TVectorT there are only two possibilities:

TVectorT< Element > & 	Use (Int_t n, Element *data) 
const TVectorT< Element > & 	Use (Int_t n, const Element *data) const

Your code obviously calls the latter one, since you define a constant pointer of type Element.
But none of the two function are applicable to my code, since I did not define a pointer of type Element but a normal variable.

Maybe you can also help with that, so that I really understand what was going wrong.

Thanks a lot!
Christoph

PS: Yes, that “Write” should have been outside the for loop.

double[] is implicitly convertible to double* (because in C and C++, arrays are pointers to the beginning of the array)

1 Like

Aah, I see.

Again, thanks a lot for your help! This forum is great!

1 Like