Best practice: Using `new` in .Add methods

_ROOT Version:v6.24.06
Platform: MacOS “Big Sur” 11.6.5
Compiler: Apple clang version 13.0.0 (clang-1300.0.29.30)


I regularly fight segmentation faults and I’d like to lessen the amount of those bugs I have to fight by having good habits in the way I do things and one question I have is a comparison of using new with and without a variable that I handle to store that address if I’m using something from ROOT that is a type of container.

I would love to write a general case using TList, but I don’t handle them bare, so I’m using TMultiGraph as it contains multiple ROOT objects of type TGraph. In an answer, please regard if the behavior is because of TList and I should expect it for all containers that rely on TList.

With that preface, my question is: Which of the following is “best”?
Or is this always an application specific question? Maybe instead: What are relative dis/advantages and respective use cases between these implementations?

Note for all the below code:


  • goal is to plot given data using a TMultiGraph
  • the code shown below is at function scope
  • let there be 2 variables at a much higher scope (possibly global)
    • the TMultiGraph pointer, auto mg = new TMultiGraph()
    • the data, vector<pair<vector<Double_t>,vector<Double_t>>> xy_data

Method 1 (user-based): Most user-based memory management

// allocate
std::vector<TGraph*> g_list(n_graphs);

for (const auto& [x, y] : xy_data){
 // init data for graph i
  g_list[i] = new TGraph(x.size(), x.data(), y.data());
}

// associate to ROOT container
for (const auto& g : g_list){
  mg->Add(g);
}

mg->Draw();

// free
for (const auto g : g_list)
  delete g;

Method 2 (STL-based): Rely on STL for containers

// allocate TMultiGraph and TGraphs but let stl handle memory for TGraphs
std::vector<TGraph> g_list();

for (const auto& [x, y] : xy_data){
 // init data for graph i
  g_list.emplace_back(x.size(), x.data(), y.data()));
}


// associate to ROOT container
for (const auto&& g : g_list){
  mg->Add(&g);
}

mg->Draw();

// frees at end of scope with STL ~vector<>

Method 3 (ROOT-based): Rely on ROOT containers completely (not sure what it will/won’t do)

// associate to ROOT container
for (const auto& [x, y] : xy_data){
 // init data for i-th TGraph
  mg->Add(new TGraph(x.size(), x.data(), y.data()));
}

mg->Draw();

// ~TMultiGraph frees when mg goes out of scope

Method 1 requires user to delete which is a chance for memory leaks. I personally want to avoid this. But I see the advantage of having the each TGraph easily accessible if I wanted to pass it to another function before I called mg->Draw(). I see this style very often.
Method 2 doesn’t require user to delete anymore. It has overhead to store the list of TGraphs, but again, I have a the TGraphs easily accessible. I don’t see this often.
Method 3 is pure ROOT, so I feel like it’s what I should be using every time, BUT I don’t see this at all and I feel like that’s not good, the destructor for TList looks like it wants it to be done this way.

What should I aim to do?

And a last note, why might I want to have mg not be dynamic memory and just have mg be of type TMultiGraph?

Hi @orionyeung001,

Regarding method (1) that you described above, you might also use a std::unique_ptr or a std::shared_ptr, instead of a raw pointer, to avoid having to free memory manually.

Additionally, and I would like @couet to confirm, in either method (1) or (2), you have to make sure that
the TGraphs added to the TMultiGraph are kept alive during the lifetime of the TMultiGraph object.

For method (3), it is unclear to me whether (and when), according to ROOT’s ownership model, the added TGraphs will be freed. Maybe @couet or @moneta can give some hints here.

Cheers,
J.

1 Like

TMultiGraphdoesn not own the TGraphs added to it. when you delete it does not delete TGraphs.

@couet TMultiGraph::Add

Yes I was wrong, that’s the opposite … my bad … sorry.