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