mist-hep 0.1.0
ROOT-backed analysis helpers built on mist
Loading...
Searching...
No Matches
owned.h
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2//
3// mist/hep/owned.h — correct ownership for ROOT objects.
4//
5// ROOT's object model fights RAII: a TH1 registers itself in the current
6// gDirectory on construction, so a TFile that is open at construction time
7// will *also* own the histogram and delete it on close — double-freeing
8// anything that wrapped it in a std::unique_ptr. This module encodes ROOT's
9// ownership rules in one place so callers do not have to.
10//
11// The contract is "detach-on-create, own-via-smart-pointer":
12// - Objects produced by make()/clone()/adopt() are detached from ROOT's
13// global registries (histograms: SetDirectory(nullptr)).
14// - A detached object is owned solely by its root_ptr; the root_deleter
15// deletes it normally.
16// - Persisting is still safe: TObject::Write() on a detached object
17// serialises a *copy* into the file — it does not transfer ownership —
18// so the root_ptr remains the sole owner and there is no double free.
19//
20// Scope of the guarantee: this makes *ownership* safe (no double-free, no
21// leak). It does NOT prevent use-after-free of a dangling TObject* or any
22// thread-safety issue — hence "owned", not "memory".
23//
24#pragma once
25
26#include <memory>
27#include <type_traits>
28#include <utility>
29
30#include <TObject.h>
31#include <TH1.h>
32
34
35// ---------------------------------------------------------------------------
36// root_deleter: ownership-correct deletion for any TObject-derived type.
37//
38// A single runtime-dispatch deleter (rather than per-type specialisations):
39// the per-call dynamic_cast cost is irrelevant for analysis-lifetime objects,
40// and one deleter keeps root_ptr<T> a single coherent type family.
41//
42// Histograms are detached from any directory before deletion as a safety net
43// (make/clone/adopt already detach at creation; this covers root_ptr<T>
44// built directly from a raw pointer). TF1 removes itself from
45// gROOT's function list in its own destructor, and TGraph is not registered
46// anywhere, so both need only a plain delete.
47// ---------------------------------------------------------------------------
49 void operator()(TObject* object) const noexcept
50 {
51 if (!object) return;
52 if (auto* histogram = dynamic_cast<TH1*>(object))
53 histogram->SetDirectory(nullptr);
54 delete object;
55 }
56};
57
58// Owning handle for a ROOT object with ownership-correct deletion.
59template <typename T>
60using root_ptr = std::unique_ptr<T, root_deleter>;
61
62// ---------------------------------------------------------------------------
63// make: construct a T from the given arguments, detach it if it is a
64// histogram, and return sole ownership.
65// ---------------------------------------------------------------------------
66template <typename T, typename... Args>
68{
69 static_assert(std::is_base_of_v<TObject, T>,
70 "mist::hep::owned::make requires a TObject-derived type");
71 auto* raw = new T(std::forward<Args>(args)...);
72 if constexpr (std::is_base_of_v<TH1, T>) raw->SetDirectory(nullptr);
73 return root_ptr<T>(raw);
74}
75
76// ---------------------------------------------------------------------------
77// clone: deep-copy an existing object via TObject::Clone, detach the copy if
78// it is a histogram, and return sole ownership of the copy.
79// ---------------------------------------------------------------------------
80template <typename T>
82{
83 static_assert(std::is_base_of_v<TObject, T>,
84 "mist::hep::owned::clone requires a TObject-derived type");
85 auto* raw = static_cast<T*>(source.Clone());
86 if constexpr (std::is_base_of_v<TH1, T>) raw->SetDirectory(nullptr);
87 return root_ptr<T>(raw);
88}
89
90// ---------------------------------------------------------------------------
91// adopt: take ownership of an existing raw pointer (e.g. one returned by a
92// ROOT API), detaching it if it is a histogram. Pass a pointer you would
93// otherwise have to `delete` yourself; afterwards the root_ptr owns it.
94// ---------------------------------------------------------------------------
95template <typename T>
97{
98 static_assert(std::is_base_of_v<TObject, T>,
99 "mist::hep::owned::adopt requires a TObject-derived type");
100 if (raw) {
101 if constexpr (std::is_base_of_v<TH1, T>) raw->SetDirectory(nullptr);
102 }
103 return root_ptr<T>(raw);
104}
105
106// ---------------------------------------------------------------------------
107// scoped_directory_off: RAII guard that disables TH1 directory auto-attachment
108// for its lifetime and restores the previous setting on exit. Use around a
109// block that creates many histograms through ROOT APIs you do not control, so
110// none of them attach to the current gDirectory:
111//
112// {
113// mist::hep::owned::scoped_directory_off guard;
114// // every TH1 created here is born detached
115// }
116// ---------------------------------------------------------------------------
118public:
120 {
121 TH1::AddDirectory(false);
122 }
123 ~scoped_directory_off() { TH1::AddDirectory(previous_); }
124
129
130private:
131 bool previous_;
132};
133
134} // namespace mist::hep::owned
scoped_directory_off & operator=(const scoped_directory_off &)=delete
~scoped_directory_off()
Definition owned.h:123
scoped_directory_off()
Definition owned.h:119
scoped_directory_off(const scoped_directory_off &)=delete
scoped_directory_off & operator=(scoped_directory_off &&)=delete
scoped_directory_off(scoped_directory_off &&)=delete
Definition owned.h:33
root_ptr< T > clone(const T &source)
Definition owned.h:81
std::unique_ptr< T, root_deleter > root_ptr
Definition owned.h:60
root_ptr< T > adopt(T *raw)
Definition owned.h:96
root_ptr< T > make(Args &&... args)
Definition owned.h:67
Definition owned.h:48
void operator()(TObject *object) const noexcept
Definition owned.h:49