Skip to content

Conversation

@guitargeek
Copy link
Contributor

@guitargeek guitargeek commented Jun 28, 2025

The TTree class already deletes the copy constructor and the copy assignment, but users might still be tempted to use the virtual copy constructor or copy assignment provided by TObject::Clone() and TObject::Copy(). But these implementations also make no sense to the TTree.

To avoid user confusion, this commit suggests to override these methods in TTree such that they thow an exception and tell you what to use instead.

Prevents reports like this one in the future:

@github-actions
Copy link

github-actions bot commented Jun 28, 2025

Test Results

    22 files      22 suites   3d 21h 59m 6s ⏱️
 3 689 tests  3 688 ✅ 0 💤 1 ❌
79 225 runs  79 223 ✅ 1 💤 1 ❌

For more details on these failures, see this check.

Results for commit 099cfa9.

♻️ This comment has been updated with latest results.

@ferdymercury
Copy link
Collaborator

Tangentially related: #9252

@pcanal
Copy link
Member

pcanal commented Jul 9, 2025

Note that TTree::Clone is used (intentionally) internally to implement CloneTTree.
I.e. the CI failures (at least most of them) are related and require further adjustment to the code.

Comment on lines 369 to 370
std::unique_ptr<TTree> tClone{static_cast<TTree*>(const_cast<TTree*>(t)->CloneTree())};
tClone->SetDirectory(nullptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider setting gDirectory to nullptr (via the TContext) to ensure that the TTree is really in memory.

Suggested change
std::unique_ptr<TTree> tClone{static_cast<TTree*>(const_cast<TTree*>(t)->CloneTree())};
tClone->SetDirectory(nullptr);
{
TDirectory::TContext ctx{nullptr};
std::unique_ptr<TTree> tClone{static_cast<TTree*>(const_cast<TTree*>(t)->CloneTree())};
}

but humm ... the previous code and the new code are doing different things (not clear what the 'real' intent is or should have been).

old: Copy just the TTree data members themselves and attach the copy to the same file:
net effect: There is 2 concurrent ways to look at the same data in the same file. Each copy of the TTree can have their own branch address and their own TTreeCache. They don't affect each other (no change in address and no changing where the cursor is).

new: Copy the tree and all its data .. but in the same file (if it is writeable) well actually in whatever file is current This is very slow if the TTree is big. The 2 trees do not affect each others but they also have duplicated (potential large) data.

proposed: Load all the data in memory after possibly a slow copy. This may or may not fit in memory. (To speeds the copy up, one could consider attaching to a TMemFile).

So really the essential question before deciding which way to go is to understand what was the intent/point of the cloning of the meta data and what are the performance and memory usage limitation that this routine needs to fit in.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::unique_ptr<TTree> tClone{static_cast<TTree*>(const_cast<TTree*>(t)->CloneTree())};
tClone->SetDirectory(nullptr);
std::unique_ptr<TTree> tClone{static_cast<TTree*>(t->GetDirectory()->CloneObject(t));

is another option ...


TObject *TTree::Clone(const char *) const
{
throw std::runtime_error("TObject::Clone() is not supported by TTree! Use TTree::CloneTree().");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: I think this is the first exception thrown in this file :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Philippe and I think it shouldn't be implemented this way, rather via

Error("Clone", "Not implemented, use XXXXXXX instead.");

@guitargeek guitargeek force-pushed the ROOT-9111 branch 2 times, most recently from 9cf5898 to fb16d02 Compare July 10, 2025 12:22
// of the original, it tears it down at destruction time!
auto deleter = [](TTree* tree){tree->SetDirectory(nullptr); delete tree;};
std::unique_ptr<TTree, decltype(deleter)> tClone(static_cast<TTree*>(t->Clone()), deleter);
std::unique_ptr<TTree, decltype(deleter)> tClone{static_cast<TTree*>(t->GetDirectory()->CloneObject(t)), deleter};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the fundamental difference between tree->Clone and tree->GetDirectory()->CloneObject(tree) that makes the latter ok but the first not?


TObject *TTree::Clone(const char *) const
{
throw std::runtime_error("TObject::Clone() is not supported by TTree! Use TTree::CloneTree().");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Philippe and I think it shouldn't be implemented this way, rather via

Error("Clone", "Not implemented, use XXXXXXX instead.");

@guitargeek guitargeek force-pushed the ROOT-9111 branch 2 times, most recently from 7566084 to 3291911 Compare July 15, 2025 11:09
Copy link
Member

@vepadulano vepadulano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this work! I do not disagree with the goal of this PR, but before approval I wanted to clarify a point

From what I can see, this PR disallows calling TTree::Clone and TTree::Copy, but in practice we are still cloning TTree ourselves in our own code in two ways:

  1. via TDirectory::CloneObject
  2. by calling the base class method TNamed::Clone

I just want to make sure that before we go ahead we are aware of what we are doing and think wether there could be better alternatives. Do we need to clone the TTree in those occasions? If so, why don't we use CloneTree? At least I believe we should add some comments inplace.

Comment on lines 91 to 115
"TH1D": ("h", "h", 10, 0, 10),
"TH1K": ("h", "h", 10, 0, 10),
"TProfile": ("h", "h", 10, 0, 10),
"TH2C": ("h", "h", 10, 0, 10, 10, 0, 10),
"TH2S": ("h", "h", 10, 0, 10, 10, 0, 10),
"TH2I": ("h", "h", 10, 0, 10, 10, 0, 10),
"TH2L": ("h", "h", 10, 0, 10, 10, 0, 10),
"TH2F": ("h", "h", 10, 0, 10, 10, 0, 10),
"TH2D": ("h", "h", 10, 0, 10, 10, 0, 10),
"TH2Poly": ("h", "h", 10, 0, 10, 10, 0, 10),
"TH2PolyBin": tuple(),
"TProfile2D": ("h", "h", 10, 0, 10, 10, 0, 10),
"TProfile2PolyBin": tuple(),
"TProfile2Poly": ("h", "h", 10, 0, 10, 10, 0, 10),
"TH3C": ("h", "h", 10, 0, 10, 10, 0, 10, 10, 0, 10),
"TH3S": ("h", "h", 10, 0, 10, 10, 0, 10, 10, 0, 10),
"TH3I": ("h", "h", 10, 0, 10, 10, 0, 10, 10, 0, 10),
"TH3L": ("h", "h", 10, 0, 10, 10, 0, 10, 10, 0, 10),
"TH3F": ("h", "h", 10, 0, 10, 10, 0, 10, 10, 0, 10),
"TH3D": ("h", "h", 10, 0, 10, 10, 0, 10, 10, 0, 10),
"TProfile3D": ("h", "h", 10, 0, 10, 10, 0, 10, 10, 0, 10),
"TGraph2D": (100,),
"TEntryList": ("name", "title"),
"TEventList": ("name", "title"),
"TTree": ("name", "title"),
"TNtuple": ("name", "title", "x:y:z"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these three entries removed from the test cases?

The `TTree` class already deletes the copy constructor and the copy
assignment, but users might still be tempted to use the virtual copy
constructor or copy assignment provided by `TObject::Clone()` and
`TObject::Copy()`. But these implementations also make no sense to the
TTree.

To avoid user confusion, this commit suggests to override these methods
in TTree such that they thow an exception and tell you what to use
instead.

Prevents reports this one in the future:

  * https://its.cern.ch/jira/browse/ROOT-9111
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants