libjmmcg  release_579_6_g8cffd
A C++ library containing an eclectic mix of useful, advanced components.
shared_ptr.hpp
Go to the documentation of this file.
1 #ifndef LIBJMMCG_CORE_SHARED_PTR_HPP
2 #define LIBJMMCG_CORE_SHARED_PTR_HPP
3 
4 /******************************************************************************
5 ** Copyright © 2004 by J.M.McGuiness, coder@hussar.me.uk
6 **
7 ** This library is free software; you can redistribute it and/or
8 ** modify it under the terms of the GNU Lesser General Public
9 ** License as published by the Free Software Foundation; either
10 ** version 2.1 of the License, or (at your option) any later version.
11 **
12 ** This library is distributed in the hope that it will be useful,
13 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 ** Lesser General Public License for more details.
16 **
17 ** You should have received a copy of the GNU Lesser General Public
18 ** License along with this library; if not, write to the Free Software
19 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21 
22 #include "atomic_counter.hpp"
23 #include "exception.hpp"
24 
25 namespace jmmcg { namespace LIBJMMCG_VER_NAMESPACE {
26 
27  namespace intrusive {
28  template<class, class> class slist;
29  }
30 
31  /// The intrusive counter that an object must also inherit from for the shared_ptr class to work.
32  /**
33  A client must inherit from this class to allow the shared_ptr to operate. If the client also wants to specify at run-time which counter or deletion method is to be used, then some base in their hierarchy must also inherit from sp_counter_itf_type.
34 
35  By default the counter is a "simple" (cough) sequential counter, with the threading model specified via the lock_traits.
36 
37  \see sp_counter_itf_type, shared_ptr
38  */
39  template<
40  class Val,
41  class LkT,
42  template<class> class Del=default_delete, ///< The deleter to be used to delete the contained pointer.
43  template<class> class AtCtr=LkT::template atomic_counter_type
44  >
45  class sp_counter_type : public AtCtr<Val> {
46  public:
47  using base_t=sp_counter_itf_type<Val>;
48  using value_type=typename base_t::value_type;
49  using atomic_ctr_t=AtCtr<value_type>;
50  using lock_traits=LkT; ///< This does not have to be the same as the atomic_ctr_t, as we may want the flexibility to deal with the type differently in this case.
51  /// Make sure the correct object deletion mechanism is used.
52  /**
53  Note this this allows the user to specify multiple features:
54  1. That the correct deleter is used with the allocator.
55  2. That if the static type of the object is known at compile-time the deleter can be used with little or no performance loss.
56  3. That the deleter may be dynamically specified in a derived class, allowing the share_ptr to contain objects that also vary by the specific deleter that the derived type will use. For example there may be a collection of base-pointers, the derived objects of which may be dynamically, placement-new or stack-allocated and upon extracting them from the collection, the shared_ptr class can accommodate deleting all of them because of the flexibility introduced in sp_counter_itf_type::deleter().
57 
58  \see sp_counter_itf_type::deleter_t, sp_counter_itf_type::deleter()
59  */
60  typedef Del<sp_counter_type> deleter_t;
61 
62  /**
63  To assist in allowing compile-time computation of the algorithmic order of the threading model.
64  */
65  static constexpr ppd::generic_traits::memory_access_modes memory_access_mode=atomic_ctr_t::memory_access_mode;
66 
67  static_assert((std::is_integral<typename atomic_ctr_t::value_type>::value && std::is_signed<typename atomic_ctr_t::value_type>::value), "The input type must be a signed integral type as defined in 3.9.1 of the Standard.");
68 
69  virtual ~sp_counter_type() noexcept(true)=default;
70 
71  /// Call the correct deleter_t object to delete the object.
72  /**
73  Note that we are calling the dtor on the object from within a virtual function, which is allowed. (The converse is not, of course.)
74  */
75  void deleter() override {
76  deleter_t().operator()(this);
77  }
78 
79  value_type sp_count() const noexcept(true) override final {
80  return ctr_.get();
81  }
82  typename atomic_ctr_t::value_type sp_acquire() noexcept(true) override final {
83  const typename atomic_ctr_t::value_type ret=++ctr_;
84  return ret;
85  }
86  bool sp_release() noexcept(true) override final {
87  return --ctr_==0;
88  }
89 
90  bool __fastcall operator<(const value_type v) const noexcept(true) override {
91  return ctr_<v;
92  }
93  bool __fastcall operator>(const value_type v) const noexcept(true) override {
94  return ctr_>v;
95  }
96  bool __fastcall operator>=(const value_type v) const noexcept(true) override final {
97  return ctr_>=v;
98  }
99 
100  tstring
101  sp_to_string() const noexcept(false) override final {
102  tostringstream os;
103  os<<"count="<<ctr_;
104  return os.str();
105  }
106 
107  protected:
108  constexpr sp_counter_type() noexcept(true) {}
109 
110  private:
111  template<class, class> friend class shared_ptr;
112  template<class, class> friend class intrusive::slist;
113 
114  mutable atomic_ctr_t ctr_;
115  };
116 
117  /// A shared pointer-type that has threading specified as a trait, and uses an intrusive count to save on calls to the memory allocator, which could cause excessive contention on the memory manager in a multi-threaded environment.
118  /**
119  I don't use boost::shared_ptr nor std::shared_ptr as they are insufficiently flexible for my purposes: the multi-threading model they use is not a trait, moreover their counters & deleters can only be specified at compile-time, not also at run-time.
120  This class uses a lock-free implementation based upon the careful use of the sp_counter_type that uses a specialised atomic_counter_type.
121  For performance tests see 'examples/shared_ptr_parallel.cpp'.
122 
123  \see sp_counter_itf_type, sp_counter_type
124  */
125  template<
126  class V, ///< The type to be controlled, which must implement the sp_counter_itf_type interface, which would also specify the threading model which, by default, is single-threaded, so no cost would be paid. This allows the counter-type to be specified at either compile or run-time.
127  class LkT ///< The lock_traits that provide the (potentially) atomic operations to be used upon pointers to the value_type.
128  >
129  class shared_ptr final {
130  public:
131  using value_type=V; ///< A convenience typedef to the type to be controlled.
132  using element_type=value_type; ///< A convenience typedef to the type to be controlled.
133  using lock_traits=LkT; ///< This does not have to be the same as the element_type, as it may not contain any lock_traits, or we may want the flexibility to deal with the type differently in this case.
134  /// The (potentially) lock-free pointer type.
135  /**
136  We need the operations on the contained pointer (to the managed object) to be atomic because the move operations might occur on more than one thread, and therefore there is a race condition on non-SMP architectures, which is avoided if (in the implementation) the operations on the contained pointer are atomic.
137  */
138  using atomic_ptr_t=typename lock_traits::template atomic<value_type *>;
139  /// The counter interface to be used on the controlled type, which allows the counter-type to be specified at compile or run-time.
140  using base_ctr_type=sp_counter_itf_type<long>;
141  using ctr_type=typename V::atomic_ctr_t;
142  using exception_type=typename lock_traits::exception_type;
143  /// Make sure the correct object-deletion mechanism is used.
144  /**
145  \see sp_counter_itf_type::deleter_t, sp_counter_itf_type::deleter(), ~shared_ptr()
146  */
147  using deleter_t=typename value_type::deleter_t;
148  /// The no-op atomic counter does nothing, therefore the shared_ptr must not manage the memory of such objects...
149  /**
150  \see noop_atomic_ctr
151  */
152  using no_ref_counting=typename lock_traits::template noop_atomic_ctr<typename ctr_type::value_type>;
153  /**
154  If you get a compilation issue here, check you aren't using std::default_delete, but libjmmcg::default_delete instead.
155 
156  \see libjmmcg::default_delete
157  */
158  using no_deletion=noop_dtor<typename value_type::deleter_t::element_type>;
159  /**
160  To assist in allowing compile-time computation of the algorithmic order of the threading model.
161  */
163  atomic_ptr_t::memory_access_mode==ppd::generic_traits::memory_access_modes::crew_memory_access
164  && ctr_type::memory_access_mode==ppd::generic_traits::memory_access_modes::crew_memory_access
167  );
168 
169  BOOST_MPL_ASSERT((std::is_base_of<base_ctr_type, value_type>));
170  static_assert(!std::is_same<base_ctr_type, no_ref_counting>::value || std::is_same<deleter_t, no_deletion>::value, "If not refcounted, then the object must not have a defined deleter, as it should be stack-based.");
171 
172  /**
173  Decrement the reference count & possibly delete the object, via calling value_type::delete() which uses the value_type::deleter_t method to delete the object & release the memory.
174  */
175  atomic_ptr_t __fastcall release() noexcept(true);
176 
177  constexpr __stdcall
178  shared_ptr() noexcept(true) {}
179 
180  /**
181  \param ptr Note that this ptr could have a non-zero reference count, and this ctor will take ownership of the ptr, respecting that reference count, only deleting the ptr if it reaches zero.
182  */
183  explicit __stdcall
184  shared_ptr(value_type *ptr) noexcept(true);
185  /**
186  \param ptr Note that this ptr could have a non-zero reference count, and this ctor will take ownership of the ptr, respecting that reference count, only deleting the ptr if it reaches zero. The type of the parameter must be the same type or a class non-privately derived from value_type.
187  */
188  template<class V1> explicit __stdcall
189  shared_ptr(V1 *ptr) noexcept(true);
190  /**
191  \param ptr Note that this ptr could have a non-zero reference count, and this ctor will take ownership of the ptr, respecting that reference count, only deleting the ptr if it reaches zero.
192  */
193  explicit __stdcall
194  shared_ptr(atomic_ptr_t const &ptr) noexcept(true);
195  /**
196  \param ptr Note that this ptr could have a non-zero reference count, and this ctor will take ownership of the ptr, respecting that reference count, only deleting the ptr if it reaches zero.
197  */
198  template<class V1> explicit __stdcall
199  shared_ptr(sp_counter_itf_type<V1> const &ptr) noexcept(true);
200  /**
201  \param ptr Note that this ptr could have a non-zero reference count, and this ctor will take ownership of the ptr, respecting that reference count, only deleting the ptr if it reaches zero.
202  */
203  template<class V1, template<class> class At> explicit __stdcall
204  shared_ptr(At<V1*> const &ptr) noexcept(true);
205  /**
206  \param ptr Note that this ptr could have a non-zero reference count, and this ctor will take ownership of the ptr, respecting that reference count, only deleting the ptr if it reaches zero.
207  */
208  explicit __stdcall
209  shared_ptr(std::unique_ptr<value_type, deleter_t> &ptr) noexcept(true);
210  /**
211  \param ptr Note that this ptr could have a non-zero reference count, and this ctor will take ownership of the ptr, respecting that reference count, only deleting the ptr if it reaches zero. The type of the parameter must be the same type or a class non-privately derived from value_type.
212  */
213  template<class V1> explicit __stdcall
214  shared_ptr(std::unique_ptr<V1, typename V1::deleter_t> &ptr) noexcept(true);
215  /**
216  \param ptr Note that this ptr could have a non-zero reference count, and this ctor will take ownership of the ptr, respecting that reference count, only deleting the ptr if it reaches zero.
217  */
218  explicit __stdcall
219  shared_ptr(std::unique_ptr<value_type, deleter_t> &&ptr) noexcept(true);
220  /**
221  \param ptr Note that this ptr could have a non-zero reference count, and this ctor will take ownership of the ptr, respecting that reference count, only deleting the ptr if it reaches zero. The type of the parameter must be the same type or a class non-privately derived from value_type.
222  */
223  template<class V1> explicit __stdcall
224  shared_ptr(std::unique_ptr<V1, typename V1::deleter_t> &&ptr) noexcept(true);
225  /**
226  Note that the same deleter and threading model must be specified.
227 
228  \param s The type of the parameter must be the same type or a class non-privately derived from value_type.
229  */
230  template<class V2, class LkT2> explicit JMMCG_MSVC_STDCALL_HACK
231  shared_ptr(const shared_ptr<V2, LkT2> &s) noexcept(true);
232  /**
233  Note that the same deleter and threading model must be specified.
234 
235  \param s The type of the parameter must be the same type or a class non-privately derived from value_type.
236  */
237  template<class V2, class LkT2> explicit JMMCG_MSVC_STDCALL_HACK
238  shared_ptr(shared_ptr<V2, LkT2> &&s) noexcept(true);
239 
240  shared_ptr(const shared_ptr &s) noexcept(true);
241  shared_ptr(shared_ptr &&s) noexcept(true);
242 
243  /**
244  \see release()
245  */
246  __stdcall
247  ~shared_ptr() noexcept(true);
248 
249  /**
250  Note that the same deleter and threading model must be specified.
251 
252  \param s The type of the parameter must be the same type or a class non-privately derived from value_type.
253  */
254  template<class V2, class LkT2> void
255  operator=(const shared_ptr<V2, LkT2> &s) noexcept(true);
256  /**
257  Note that the same deleter and threading model must be specified.
258 
259  \param s The type of the parameter must be the same type or a class non-privately derived from value_type.
260  */
261  template<class V2, class LkT2> void
262  operator=(shared_ptr<V2, LkT2> &&s) noexcept(true);
263 
264  void
265  operator=(const shared_ptr &s) noexcept(true);
266  void
267  operator=(shared_ptr &&s) noexcept(true);
268 
269  void __fastcall
270  reset() noexcept(true);
271 
272  constexpr bool __fastcall
273  operator==(const shared_ptr &s) const noexcept(true);
274  constexpr bool __fastcall
275  operator!=(const shared_ptr &s) const noexcept(true);
276  constexpr bool __fastcall
277  operator<(const shared_ptr &s) const noexcept(true);
278  constexpr bool __fastcall
279  operator>(const shared_ptr &s) const noexcept(true);
280  explicit constexpr
281  operator bool() const noexcept(true);
282 
283  constexpr const atomic_ptr_t &__fastcall
284  get() const noexcept(true);
285  atomic_ptr_t &__fastcall
286  get() noexcept(true);
287  constexpr const value_type & __fastcall
288  operator*() const noexcept(true);
289  value_type & __fastcall
290  operator*() noexcept(true);
291  constexpr value_type const * __fastcall
292  operator->() const noexcept(true);
293  value_type * __fastcall
294  operator->() noexcept(true);
295 
296  void swap(shared_ptr &s) noexcept(true);
297 
298  tstring to_string() const noexcept(false);
299 
300  /**
301  \todo Implement using the advice given in "Standard C++ IOStreams and Locales" by A.Langer & K.Kreft, page 170.
302  */
303  friend tostream & __fastcall operator<<(tostream &os, shared_ptr const &ptr) noexcept(false) {
304  os<<ptr.to_string();
305  return os;
306  }
307 
308  private:
309  template<class, class> friend class shared_ptr;
310  atomic_ptr_t data_;
311  };
312 
313 } }
314 
315 #include "shared_ptr_impl.hpp"
316 
317 #endif