Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
RAdoptAllocator.hxx
Go to the documentation of this file.
1 // Author: Enrico Guiraud, Enric Tejedor, Danilo Piparo CERN 01/2018
2 
3 /*************************************************************************
4  * Copyright (C) 1995-2018, Rene Brun and Fons Rademakers. *
5  * All rights reserved. *
6  * *
7  * For the licensing terms see $ROOTSYS/LICENSE. *
8  * For the list of contributors see $ROOTSYS/README/CREDITS. *
9  *************************************************************************/
10 
11 #ifndef ROOT_TADOPTALLOCATOR
12 #define ROOT_TADOPTALLOCATOR
13 
14 #include <iostream>
15 #include <memory>
16 
17 namespace ROOT {
18 namespace Detail {
19 namespace VecOps {
20 
21 /**
22 \class ROOT::Detail::VecOps::RAdoptAllocator
23 \ingroup vecops
24 \brief RAdoptAllocator can provide a view on already allocated memory.
25 
26 The RAdoptAllocator behaves like the standard allocator, and, as such, can be used to create
27 stl containers. In addition, it behaves as if it allocated a certain memory region which
28 is indeed not managed by it, but rather is "adopted".
29 This is most useful to take advantage of widely adopted entities such as std::vector in a
30 novel way, namely offering nice interfaces around an arbitrary memory region.
31 
32 If memory is adopted, the first allocation returns the address of this memory region. For
33 the subsequent allocations, the RAdoptAllocator behaves like a standard allocator.
34 
35 For example:
36 ~~~{.cpp}
37 std::vector<double> model {1, 2, 3};
38 unsigned int dummy;
39 RAdoptAllocator<double> alloc(model.data(), model.size());
40 std::vector<double, RAdoptAllocator<double>> v(model.size(), 0., alloc);
41 ~~~
42 Now the vector *v* is ready to be used, de facto proxying the memory of the vector *model*.
43 Upon a second allocation, the vector *v* ceases to be a proxy
44 ~~~{.cpp}
45 v.emplace_back(0.);
46 ~~~
47 now the vector *v* owns its memory as a regular vector.
48 **/
49 
50 template <typename T>
51 class RAdoptAllocator {
52 public:
53  friend class RAdoptAllocator<bool>;
54 
55  using propagate_on_container_move_assignment = std::true_type;
56  using propagate_on_container_swap = std::true_type;
57  using StdAlloc_t = std::allocator<T>;
58  using value_type = typename StdAlloc_t::value_type;
59  using pointer = typename StdAlloc_t::pointer;
60  using const_pointer = typename StdAlloc_t::const_pointer;
61  using reference = typename StdAlloc_t::reference;
62  using const_reference = typename StdAlloc_t::const_reference;
63  using size_type = typename StdAlloc_t::size_type;
64  using difference_type = typename StdAlloc_t::difference_type;
65  template <typename U>
66  struct rebind {
67  using other = RAdoptAllocator<U>;
68  };
69 
70 private:
71  enum class EAllocType : char { kOwning, kAdopting, kAdoptingNoAllocYet };
72  using StdAllocTraits_t = std::allocator_traits<StdAlloc_t>;
73  pointer fInitialAddress = nullptr;
74  EAllocType fAllocType = EAllocType::kOwning;
75  StdAlloc_t fStdAllocator;
76 
77 public:
78  /// This is the constructor which allows the allocator to adopt a certain memory region.
79  RAdoptAllocator(pointer p) : fInitialAddress(p), fAllocType(EAllocType::kAdoptingNoAllocYet){};
80  RAdoptAllocator() = default;
81  RAdoptAllocator(const RAdoptAllocator &) = default;
82  RAdoptAllocator(RAdoptAllocator &&) = default;
83  RAdoptAllocator &operator=(const RAdoptAllocator &) = default;
84  RAdoptAllocator &operator=(RAdoptAllocator &&) = default;
85  RAdoptAllocator(const RAdoptAllocator<bool> &);
86 
87  /// Construct an object at a certain memory address
88  /// \tparam U The type of the memory address at which the object needs to be constructed
89  /// \tparam Args The arguments' types necessary for the construction of the object
90  /// \param[in] p The memory address at which the object needs to be constructed
91  /// \param[in] args The arguments necessary for the construction of the object
92  /// This method is a no op if memory has been adopted.
93  template <class U, class... Args>
94  void construct(U *p, Args &&... args)
95  {
96  // We refuse to do anything since we assume the memory is already initialised
97  if (EAllocType::kAdopting == fAllocType)
98  return;
99  fStdAllocator.construct(p, args...);
100  }
101 
102  /// \brief Allocate some memory
103  /// If an address has been adopted, at the first call, that address is returned.
104  /// Subsequent calls will make "decay" the allocator to a regular stl allocator.
105  pointer allocate(std::size_t n)
106  {
107  if (n > std::size_t(-1) / sizeof(T))
108  throw std::bad_alloc();
109  if (EAllocType::kAdoptingNoAllocYet == fAllocType) {
110  fAllocType = EAllocType::kAdopting;
111  return fInitialAddress;
112  }
113  fAllocType = EAllocType::kOwning;
114  return StdAllocTraits_t::allocate(fStdAllocator, n);
115  }
116 
117  /// \brief Dellocate some memory if that had not been adopted.
118  void deallocate(pointer p, std::size_t n)
119  {
120  if (p != fInitialAddress)
121  StdAllocTraits_t::deallocate(fStdAllocator, p, n);
122  }
123 
124  template <class U>
125  void destroy(U *p)
126  {
127  if (EAllocType::kAdopting != fAllocType) {
128  fStdAllocator.destroy(p);
129  }
130  }
131 
132  bool operator==(const RAdoptAllocator<T> &other)
133  {
134  return fInitialAddress == other.fInitialAddress && fAllocType == other.fAllocType &&
135  fStdAllocator == other.fStdAllocator;
136  }
137 
138  bool operator!=(const RAdoptAllocator<T> &other) { return !(*this == other); }
139 
140  size_type max_size() const { return fStdAllocator.max_size(); };
141 };
142 
143 // The different semantics of std::vector<bool> make memory adoption through a
144 // custom allocator more complex -- namely, RAdoptAllocator<bool> must be rebindable
145 // to RAdoptAllocator<unsigned long>, but if adopted memory is really a buffer of
146 // bools reinterpretation of the buffer is not going to work. As a workaround,
147 // RAdoptAllocator<bool> is specialized to be a simple allocator that forwards calls
148 // to std::allocator and never adopts memory.
149 template <>
150 class RAdoptAllocator<bool> {
151  std::allocator<bool> fStdAllocator;
152 
153 public:
154  template <typename U>
155  struct rebind {
156  using other = RAdoptAllocator<U>;
157  };
158 
159  template <typename T>
160  friend class RAdoptAllocator;
161 
162  using StdAlloc_t = std::allocator<bool>;
163  using value_type = typename StdAlloc_t::value_type;
164  using pointer = typename StdAlloc_t::pointer;
165  using const_pointer = typename StdAlloc_t::const_pointer;
166  using reference = typename StdAlloc_t::reference;
167  using const_reference = typename StdAlloc_t::const_reference;
168  using size_type = typename StdAlloc_t::size_type;
169  using difference_type = typename StdAlloc_t::difference_type;
170 
171  RAdoptAllocator() = default;
172  RAdoptAllocator(const RAdoptAllocator &) = default;
173 
174  template <typename U>
175  RAdoptAllocator(const RAdoptAllocator<U> &o) : fStdAllocator(o.fStdAllocator)
176  {
177  if (o.fAllocType != RAdoptAllocator<U>::EAllocType::kOwning)
178  throw std::runtime_error("Cannot rebind owning RAdoptAllocator");
179  }
180 
181  bool *allocate(std::size_t n) { return fStdAllocator.allocate(n); }
182 
183  template <typename U, class... Args>
184  void construct(U *p, Args &&... args)
185  {
186  fStdAllocator.construct(p, std::forward<Args>(args)...);
187  }
188 
189  void deallocate(bool *p, std::size_t s) noexcept { fStdAllocator.deallocate(p, s); }
190 
191  template <class U>
192  void destroy(U *p)
193  {
194  fStdAllocator.destroy(p);
195  }
196 
197  bool operator==(const RAdoptAllocator &) { return true; }
198 
199  bool operator!=(const RAdoptAllocator &) { return false; }
200 };
201 
202 template <typename T>
203 RAdoptAllocator<T>::RAdoptAllocator(const RAdoptAllocator<bool> &o) : fStdAllocator(o.fStdAllocator) {}
204 
205 } // End NS VecOps
206 } // End NS Detail
207 } // End NS ROOT
208 
209 #endif